mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-12 20:00:28 +01:00
Compare commits
3 Commits
feat/dashb
...
issue_5324
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01cc8ec1d1 | ||
|
|
8fe3f769c2 | ||
|
|
e6d8713e1c |
@@ -1364,6 +1364,7 @@ components:
|
||||
- appservice
|
||||
- containerapp
|
||||
- aks
|
||||
- sqldatabase
|
||||
type: string
|
||||
CloudintegrationtypesServiceMetadata:
|
||||
properties:
|
||||
|
||||
@@ -2655,6 +2655,7 @@ export enum CloudintegrationtypesServiceIDDTO {
|
||||
appservice = 'appservice',
|
||||
containerapp = 'containerapp',
|
||||
aks = 'aks',
|
||||
sqldatabase = 'sqldatabase',
|
||||
}
|
||||
export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
|
||||
/**
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { commaValuesParser } from 'lib/dashboardVariables/customCommaValuesParser';
|
||||
import sortValues from 'lib/dashboardVariables/sortVariableValues';
|
||||
|
||||
import type { VariableFormModel } from '../DashboardSettings/Variables/variableModel';
|
||||
import type { VariableSelection, VariableSelectionMap } from './selectionTypes';
|
||||
import DynamicSelector from './selectors/DynamicSelector';
|
||||
import QuerySelector from './selectors/QuerySelector';
|
||||
import TextSelector from './selectors/TextSelector';
|
||||
import ValueSelector from './selectors/ValueSelector';
|
||||
import styles from './VariablesBar.module.scss';
|
||||
|
||||
interface VariableSelectorProps {
|
||||
variable: VariableFormModel;
|
||||
/** All variables (Dynamic uses them to scope options by sibling selections). */
|
||||
variables: VariableFormModel[];
|
||||
/** Names this variable depends on (for Query gating). */
|
||||
parents: string[];
|
||||
/** All current selections (Query passes them as the request payload). */
|
||||
selections: VariableSelectionMap;
|
||||
selection: VariableSelection;
|
||||
onChange: (selection: VariableSelection) => void;
|
||||
}
|
||||
|
||||
/** One labelled variable control; dispatches on the variable type. */
|
||||
function VariableSelector({
|
||||
variable,
|
||||
variables,
|
||||
parents,
|
||||
selections,
|
||||
selection,
|
||||
onChange,
|
||||
}: VariableSelectorProps): JSX.Element {
|
||||
const customOptions = useMemo(
|
||||
() =>
|
||||
variable.type === 'CUSTOM'
|
||||
? sortValues(commaValuesParser(variable.customValue), variable.sort).map(
|
||||
String,
|
||||
)
|
||||
: [],
|
||||
[variable],
|
||||
);
|
||||
|
||||
const renderControl = (): JSX.Element => {
|
||||
switch (variable.type) {
|
||||
case 'TEXT':
|
||||
return (
|
||||
<TextSelector
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
testId={`variable-input-${variable.name}`}
|
||||
/>
|
||||
);
|
||||
case 'QUERY':
|
||||
return (
|
||||
<QuerySelector
|
||||
variable={variable}
|
||||
parents={parents}
|
||||
selections={selections}
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
case 'DYNAMIC':
|
||||
return (
|
||||
<DynamicSelector
|
||||
variable={variable}
|
||||
variables={variables}
|
||||
selections={selections}
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
case 'CUSTOM':
|
||||
default:
|
||||
return (
|
||||
<ValueSelector
|
||||
options={customOptions}
|
||||
multiSelect={variable.multiSelect}
|
||||
showAllOption={variable.showAllOption}
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
testId={`variable-select-${variable.name}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.variable} data-testid={`variable-${variable.name}`}>
|
||||
<Typography.Text className={styles.label}>${variable.name}</Typography.Text>
|
||||
{renderControl()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VariableSelector;
|
||||
@@ -1,29 +0,0 @@
|
||||
.bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 12px 16px;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.variable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.select {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.input {
|
||||
min-width: 160px;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { DashboardtypesGettableDashboardV2DTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { useVariableSelection } from './useVariableSelection';
|
||||
import VariableSelector from './VariableSelector';
|
||||
import styles from './VariablesBar.module.scss';
|
||||
|
||||
interface VariablesBarProps {
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime variable selector bar shown above the panels. Renders one control per
|
||||
* dashboard variable; selections live in the store + URL (never the spec).
|
||||
*/
|
||||
function VariablesBar({ dashboard }: VariablesBarProps): JSX.Element | null {
|
||||
const { variables, dependencyData, selection, setSelection } =
|
||||
useVariableSelection(dashboard);
|
||||
|
||||
if (variables.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.bar} data-testid="dashboard-variables-bar">
|
||||
{variables.map((variable) => (
|
||||
<VariableSelector
|
||||
key={variable.name}
|
||||
variable={variable}
|
||||
variables={variables}
|
||||
parents={dependencyData.parentGraph[variable.name] ?? []}
|
||||
selections={selection}
|
||||
selection={
|
||||
selection[variable.name] ?? {
|
||||
value: variable.multiSelect ? [] : '',
|
||||
allSelected: false,
|
||||
}
|
||||
}
|
||||
onChange={(next): void => setSelection(variable.name, next)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VariablesBar;
|
||||
@@ -1,56 +0,0 @@
|
||||
import type { VariableFormModel } from '../DashboardSettings/Variables/variableModel';
|
||||
import type { VariableSelectionMap } from './selectionTypes';
|
||||
|
||||
function formatQueryValue(val: string): string {
|
||||
const num = Number(val);
|
||||
if (!Number.isNaN(num) && Number.isFinite(num)) {
|
||||
return val;
|
||||
}
|
||||
return `'${val.replace(/'/g, "\\'")}'`;
|
||||
}
|
||||
|
||||
function buildQueryPart(attribute: string, values: string[]): string {
|
||||
const formatted = values.map(formatQueryValue);
|
||||
if (formatted.length === 1) {
|
||||
return `${attribute} = ${formatted[0]}`;
|
||||
}
|
||||
return `${attribute} IN [${formatted.join(', ')}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filter expression from the OTHER dynamic variables' current
|
||||
* selections (e.g. `k8s.namespace.name IN ['prod'] AND service = 'api'`), so a
|
||||
* dynamic variable's option list is scoped by its sibling selections. Variables
|
||||
* in the ALL state, with no selection, or non-dynamic are skipped. Ported from
|
||||
* the V1 dynamic-variable runtime.
|
||||
*/
|
||||
export function buildExistingDynamicVariableQuery(
|
||||
variables: VariableFormModel[],
|
||||
selections: VariableSelectionMap,
|
||||
currentName: string,
|
||||
): string {
|
||||
const parts: string[] = [];
|
||||
variables.forEach((variable) => {
|
||||
if (
|
||||
variable.name === currentName ||
|
||||
variable.type !== 'DYNAMIC' ||
|
||||
!variable.dynamicAttribute
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const selection = selections[variable.name];
|
||||
if (!selection || selection.allSelected) {
|
||||
return;
|
||||
}
|
||||
const raw = Array.isArray(selection.value)
|
||||
? selection.value
|
||||
: [selection.value];
|
||||
const valid = raw
|
||||
.filter((v) => v !== null && v !== undefined && v !== '')
|
||||
.map((v) => String(v));
|
||||
if (valid.length > 0) {
|
||||
parts.push(buildQueryPart(variable.dynamicAttribute, valid));
|
||||
}
|
||||
});
|
||||
return parts.join(' AND ');
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/** A user-selected variable value at runtime (not persisted to the spec). */
|
||||
export type SelectedVariableValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| (string | number | boolean)[]
|
||||
| null;
|
||||
|
||||
export interface VariableSelection {
|
||||
value: SelectedVariableValue;
|
||||
/** True when every option is selected ("ALL"); for dynamic vars value may be null. */
|
||||
allSelected: boolean;
|
||||
}
|
||||
|
||||
/** Selected values for a dashboard's variables, keyed by variable name. */
|
||||
export type VariableSelectionMap = Record<string, VariableSelection>;
|
||||
@@ -1,31 +0,0 @@
|
||||
import type {
|
||||
SelectedVariableValue,
|
||||
VariableSelection,
|
||||
VariableSelectionMap,
|
||||
} from './selectionTypes';
|
||||
|
||||
/** A selection counts as resolved (usable as a parent value) when it's non-empty. */
|
||||
export function isResolved(selection?: VariableSelection): boolean {
|
||||
if (!selection) {
|
||||
return false;
|
||||
}
|
||||
if (selection.allSelected) {
|
||||
return true;
|
||||
}
|
||||
const { value } = selection;
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0;
|
||||
}
|
||||
return value !== '' && value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
/** Flatten the selection map into the `{ name: value }` payload a query expects. */
|
||||
export function selectionToPayload(
|
||||
selection: VariableSelectionMap,
|
||||
): Record<string, SelectedVariableValue> {
|
||||
const payload: Record<string, SelectedVariableValue> = {};
|
||||
Object.entries(selection).forEach(([name, sel]) => {
|
||||
payload[name] = sel.value;
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useGetFieldValues } from 'hooks/dynamicVariables/useGetFieldValues';
|
||||
import sortValues from 'lib/dashboardVariables/sortVariableValues';
|
||||
import type { AppState } from 'store/reducers';
|
||||
import type { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import type { VariableFormModel } from '../../DashboardSettings/Variables/variableModel';
|
||||
import { buildExistingDynamicVariableQuery } from '../dynamicFilter';
|
||||
import type {
|
||||
VariableSelection,
|
||||
VariableSelectionMap,
|
||||
} from '../selectionTypes';
|
||||
import { useAutoSelect } from '../useAutoSelect';
|
||||
import ValueSelector from './ValueSelector';
|
||||
|
||||
interface DynamicSelectorProps {
|
||||
variable: VariableFormModel;
|
||||
/** All variables + current selections, to scope options by sibling dynamics. */
|
||||
variables: VariableFormModel[];
|
||||
selections: VariableSelectionMap;
|
||||
selection: VariableSelection;
|
||||
onChange: (selection: VariableSelection) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic-variable options sourced from live telemetry field values for the
|
||||
* chosen signal + attribute, scoped by the other dynamic variables' selections
|
||||
* (so e.g. `pod` narrows to the chosen `namespace`).
|
||||
*/
|
||||
function DynamicSelector({
|
||||
variable,
|
||||
variables,
|
||||
selections,
|
||||
selection,
|
||||
onChange,
|
||||
}: DynamicSelectorProps): JSX.Element {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const existingQuery = useMemo(
|
||||
() => buildExistingDynamicVariableQuery(variables, selections, variable.name),
|
||||
[variables, selections, variable.name],
|
||||
);
|
||||
|
||||
const { data, isFetching } = useGetFieldValues({
|
||||
signal: variable.dynamicSignal,
|
||||
name: variable.dynamicAttribute,
|
||||
startUnixMilli: minTime,
|
||||
endUnixMilli: maxTime,
|
||||
existingQuery: existingQuery || undefined,
|
||||
enabled: !!variable.dynamicAttribute,
|
||||
});
|
||||
|
||||
const options = useMemo(() => {
|
||||
const payload = data?.data;
|
||||
const values =
|
||||
payload?.normalizedValues ?? payload?.values?.StringValues ?? [];
|
||||
return sortValues(values, variable.sort).map(String);
|
||||
}, [data, variable.sort]);
|
||||
|
||||
useAutoSelect(variable, options, selection, onChange);
|
||||
|
||||
return (
|
||||
<ValueSelector
|
||||
options={options}
|
||||
multiSelect={variable.multiSelect}
|
||||
showAllOption={variable.showAllOption}
|
||||
loading={isFetching}
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
testId={`variable-select-${variable.name}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicSelector;
|
||||
@@ -1,89 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
|
||||
import sortValues from 'lib/dashboardVariables/sortVariableValues';
|
||||
import type { AppState } from 'store/reducers';
|
||||
import type { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import type { VariableFormModel } from '../../DashboardSettings/Variables/variableModel';
|
||||
import type {
|
||||
VariableSelection,
|
||||
VariableSelectionMap,
|
||||
} from '../selectionTypes';
|
||||
import { isResolved, selectionToPayload } from '../selectionUtils';
|
||||
import { useAutoSelect } from '../useAutoSelect';
|
||||
import ValueSelector from './ValueSelector';
|
||||
|
||||
interface QuerySelectorProps {
|
||||
variable: VariableFormModel;
|
||||
/** Names this variable's query references; it waits until they're resolved. */
|
||||
parents: string[];
|
||||
/** All current selections, fed to the query as `{ name: value }`. */
|
||||
selections: VariableSelectionMap;
|
||||
selection: VariableSelection;
|
||||
onChange: (selection: VariableSelection) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query-driven options. Dependency orchestration is declarative: the query is
|
||||
* `enabled` only once every parent is resolved, and the parent values are in the
|
||||
* query key — so it refetches automatically when a parent changes (and a cyclic
|
||||
* dependency is simply never enabled).
|
||||
*/
|
||||
function QuerySelector({
|
||||
variable,
|
||||
parents,
|
||||
selections,
|
||||
selection,
|
||||
onChange,
|
||||
}: QuerySelectorProps): JSX.Element {
|
||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const payload = useMemo(() => selectionToPayload(selections), [selections]);
|
||||
const enabled = parents.every((parent) => isResolved(selections[parent]));
|
||||
|
||||
const { data, isFetching } = useQuery(
|
||||
[
|
||||
'dashboard-variable',
|
||||
variable.name,
|
||||
variable.queryValue,
|
||||
payload,
|
||||
minTime,
|
||||
maxTime,
|
||||
],
|
||||
() =>
|
||||
dashboardVariablesQuery({
|
||||
query: variable.queryValue,
|
||||
variables: payload,
|
||||
}),
|
||||
{ enabled, refetchOnWindowFocus: false },
|
||||
);
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!data || data.statusCode !== 200 || !data.payload) {
|
||||
return [] as string[];
|
||||
}
|
||||
return sortValues(data.payload.variableValues ?? [], variable.sort).map(
|
||||
String,
|
||||
);
|
||||
}, [data, variable.sort]);
|
||||
|
||||
useAutoSelect(variable, options, selection, onChange);
|
||||
|
||||
return (
|
||||
<ValueSelector
|
||||
options={options}
|
||||
multiSelect={variable.multiSelect}
|
||||
showAllOption={variable.showAllOption}
|
||||
loading={isFetching}
|
||||
selection={selection}
|
||||
onChange={onChange}
|
||||
testId={`variable-select-${variable.name}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuerySelector;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
|
||||
import type { VariableSelection } from '../selectionTypes';
|
||||
import styles from '../VariablesBar.module.scss';
|
||||
|
||||
interface TextSelectorProps {
|
||||
selection: VariableSelection;
|
||||
onChange: (selection: VariableSelection) => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
/** Free-text variable input. */
|
||||
function TextSelector({
|
||||
selection,
|
||||
onChange,
|
||||
testId,
|
||||
}: TextSelectorProps): JSX.Element {
|
||||
return (
|
||||
<Input
|
||||
className={styles.input}
|
||||
value={typeof selection.value === 'string' ? selection.value : ''}
|
||||
placeholder="Enter a value"
|
||||
onChange={(e): void =>
|
||||
onChange({ value: e.target.value, allSelected: false })
|
||||
}
|
||||
testId={testId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default TextSelector;
|
||||
@@ -1,94 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import { CustomMultiSelect, CustomSelect } from 'components/NewSelect';
|
||||
import type { OptionData } from 'components/NewSelect/types';
|
||||
import { ALL_SELECT_VALUE } from 'container/DashboardContainer/utils';
|
||||
|
||||
import type { VariableSelection } from '../selectionTypes';
|
||||
import styles from '../VariablesBar.module.scss';
|
||||
|
||||
interface ValueSelectorProps {
|
||||
options: string[];
|
||||
multiSelect: boolean;
|
||||
showAllOption: boolean;
|
||||
loading?: boolean;
|
||||
selection: VariableSelection;
|
||||
onChange: (selection: VariableSelection) => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single/multi value picker for Custom/Query/Dynamic variables. Reuses the
|
||||
* shared NewSelect components, which provide search, the "ALL" option and
|
||||
* apply-on-close batching (so multi-select edits don't cascade per toggle).
|
||||
*/
|
||||
function ValueSelector({
|
||||
options,
|
||||
multiSelect,
|
||||
showAllOption,
|
||||
loading,
|
||||
selection,
|
||||
onChange,
|
||||
testId,
|
||||
}: ValueSelectorProps): JSX.Element {
|
||||
const optionData = useMemo<OptionData[]>(
|
||||
() => options.map((option) => ({ label: option, value: option })),
|
||||
[options],
|
||||
);
|
||||
|
||||
if (multiSelect) {
|
||||
const value = selection.allSelected
|
||||
? ALL_SELECT_VALUE
|
||||
: (Array.isArray(selection.value) ? selection.value : []).map(String);
|
||||
return (
|
||||
<CustomMultiSelect
|
||||
className={styles.select}
|
||||
data-testid={testId}
|
||||
options={optionData}
|
||||
value={value}
|
||||
loading={loading}
|
||||
showSearch
|
||||
placeholder="Select value"
|
||||
enableAllSelection={showAllOption}
|
||||
onChange={(next): void => {
|
||||
const values = Array.isArray(next)
|
||||
? next.map(String)
|
||||
: next
|
||||
? [String(next)]
|
||||
: [];
|
||||
if (values.length === 0) {
|
||||
onChange({ value: [], allSelected: false });
|
||||
return;
|
||||
}
|
||||
// CustomMultiSelect emits the full value set when ALL is picked.
|
||||
const isAll =
|
||||
showAllOption &&
|
||||
options.length > 0 &&
|
||||
options.every((option) => values.includes(option));
|
||||
onChange({ value: values, allSelected: isAll });
|
||||
}}
|
||||
onClear={(): void => onChange({ value: [], allSelected: false })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomSelect
|
||||
className={styles.select}
|
||||
data-testid={testId}
|
||||
options={optionData}
|
||||
value={
|
||||
selection.value == null || Array.isArray(selection.value)
|
||||
? undefined
|
||||
: String(selection.value)
|
||||
}
|
||||
loading={loading}
|
||||
showSearch
|
||||
placeholder="Select value"
|
||||
onChange={(next): void =>
|
||||
onChange({ value: next == null ? '' : String(next), allSelected: false })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ValueSelector;
|
||||
@@ -1,41 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import type { VariableFormModel } from '../DashboardSettings/Variables/variableModel';
|
||||
import type { VariableSelection } from './selectionTypes';
|
||||
|
||||
/**
|
||||
* When fetched options arrive and the current selection isn't one of them,
|
||||
* auto-pick the variable's default (if present in the options) or the first
|
||||
* option — so dependent children always have a usable parent value.
|
||||
*/
|
||||
export function useAutoSelect(
|
||||
variable: VariableFormModel,
|
||||
options: string[],
|
||||
selection: VariableSelection,
|
||||
onChange: (selection: VariableSelection) => void,
|
||||
): void {
|
||||
useEffect(() => {
|
||||
if (options.length === 0 || selection.allSelected) {
|
||||
return;
|
||||
}
|
||||
const current = selection.value;
|
||||
const isValid = Array.isArray(current)
|
||||
? current.length > 0 && current.every((c) => options.includes(String(c)))
|
||||
: current !== '' &&
|
||||
current !== null &&
|
||||
current !== undefined &&
|
||||
options.includes(String(current));
|
||||
if (isValid) {
|
||||
return;
|
||||
}
|
||||
const fallback = (variable.defaultValue as { value?: string } | undefined)
|
||||
?.value;
|
||||
const initial =
|
||||
fallback && options.includes(fallback) ? fallback : options[0];
|
||||
onChange({
|
||||
value: variable.multiSelect ? [initial] : initial,
|
||||
allSelected: false,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [options]);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { parseAsJson, useQueryState } from 'nuqs';
|
||||
import type { DashboardtypesGettableDashboardV2DTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { dtoToFormModel } from '../DashboardSettings/Variables/variableAdapters';
|
||||
import type { VariableFormModel } from '../DashboardSettings/Variables/variableModel';
|
||||
import { selectVariableValues } from '../store/slices/variableSelectionSlice';
|
||||
import { useDashboardStore } from '../store/useDashboardStore';
|
||||
import type {
|
||||
SelectedVariableValue,
|
||||
VariableSelection,
|
||||
VariableSelectionMap,
|
||||
} from './selectionTypes';
|
||||
import {
|
||||
computeVariableDependencies,
|
||||
type VariableDependencyData,
|
||||
} from './variableDependencies';
|
||||
|
||||
/** URL sentinel for an "ALL values selected" state (matches V1). */
|
||||
export const ALL_SELECTED = '__ALL__';
|
||||
|
||||
/** `?variables=` holds `{ [name]: value }` (ALL encoded as the sentinel). */
|
||||
const variablesUrlParser = parseAsJson<Record<string, SelectedVariableValue>>(
|
||||
(v) =>
|
||||
typeof v === 'object' && v !== null
|
||||
? (v as Record<string, SelectedVariableValue>)
|
||||
: null,
|
||||
);
|
||||
|
||||
function defaultSelection(model: VariableFormModel): VariableSelection {
|
||||
const def = (
|
||||
model.defaultValue as { value?: SelectedVariableValue } | undefined
|
||||
)?.value;
|
||||
if (def !== undefined && def !== null && def !== '') {
|
||||
return { value: def, allSelected: false };
|
||||
}
|
||||
return { value: model.multiSelect ? [] : '', allSelected: false };
|
||||
}
|
||||
|
||||
function fromUrlValue(raw: SelectedVariableValue): VariableSelection {
|
||||
return raw === ALL_SELECTED
|
||||
? { value: null, allSelected: true }
|
||||
: { value: raw, allSelected: false };
|
||||
}
|
||||
|
||||
interface UseVariableSelection {
|
||||
variables: VariableFormModel[];
|
||||
dependencyData: VariableDependencyData;
|
||||
selection: VariableSelectionMap;
|
||||
setSelection: (name: string, selection: VariableSelection) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime variable selection: derives the variable list from the spec, seeds
|
||||
* each value from URL → localStorage(store) → default, and persists changes to
|
||||
* both the store and the URL. Never writes to the dashboard spec.
|
||||
*/
|
||||
export function useVariableSelection(
|
||||
dashboard: DashboardtypesGettableDashboardV2DTO,
|
||||
): UseVariableSelection {
|
||||
const dashboardId = dashboard.id ?? '';
|
||||
|
||||
const variables = useMemo(
|
||||
() => (dashboard.spec?.variables ?? []).map(dtoToFormModel),
|
||||
[dashboard.spec?.variables],
|
||||
);
|
||||
const dependencyData = useMemo(
|
||||
() => computeVariableDependencies(variables),
|
||||
[variables],
|
||||
);
|
||||
|
||||
const selection = useDashboardStore(selectVariableValues(dashboardId));
|
||||
const setVariableValue = useDashboardStore((s) => s.setVariableValue);
|
||||
const setVariableValues = useDashboardStore((s) => s.setVariableValues);
|
||||
|
||||
const [urlValues, setUrlValues] = useQueryState(
|
||||
'variables',
|
||||
variablesUrlParser.withOptions({ history: 'replace' }),
|
||||
);
|
||||
|
||||
// Seed selections for this dashboard: URL wins, then persisted store, then default.
|
||||
useEffect(() => {
|
||||
if (!dashboardId || variables.length === 0) {
|
||||
return;
|
||||
}
|
||||
// `selection` here is the persisted (localStorage) map on mount — the
|
||||
// effect deliberately doesn't depend on it, so seeding runs once per set.
|
||||
const stored = selection;
|
||||
const seeded: VariableSelectionMap = {};
|
||||
variables.forEach((variable) => {
|
||||
const urlValue = urlValues?.[variable.name];
|
||||
if (urlValue !== undefined) {
|
||||
seeded[variable.name] = fromUrlValue(urlValue);
|
||||
} else if (stored[variable.name]) {
|
||||
seeded[variable.name] = stored[variable.name];
|
||||
} else {
|
||||
seeded[variable.name] = defaultSelection(variable);
|
||||
}
|
||||
});
|
||||
setVariableValues(dashboardId, seeded);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dashboardId, variables]);
|
||||
|
||||
const setSelection = useCallback(
|
||||
(name: string, next: VariableSelection): void => {
|
||||
setVariableValue(dashboardId, name, next);
|
||||
void setUrlValues((prev) => ({
|
||||
...(prev ?? {}),
|
||||
[name]: next.allSelected ? ALL_SELECTED : next.value,
|
||||
}));
|
||||
},
|
||||
[dashboardId, setVariableValue, setUrlValues],
|
||||
);
|
||||
|
||||
return { variables, dependencyData, selection, setSelection };
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
import { textContainsVariableReference } from 'lib/dashboardVariables/variableReference';
|
||||
|
||||
import type { VariableFormModel } from '../DashboardSettings/Variables/variableModel';
|
||||
|
||||
/**
|
||||
* Inter-variable dependency graph for runtime selection. A QUERY variable
|
||||
* "depends on" another variable when its query text references that variable
|
||||
* (`{{.name}}`, `{{name}}`, `$name`, `[[name]]`). When a variable's value
|
||||
* changes, its dependent QUERY variables must refetch. Ported from the V1
|
||||
* dashboard-variables runtime; operates on the V2 flat variable model.
|
||||
*/
|
||||
|
||||
export type VariableGraph = Record<string, string[]>;
|
||||
|
||||
export interface VariableDependencyData {
|
||||
/** Topological order of variables (parents before children). */
|
||||
order: string[];
|
||||
/** Direct children (dependents) of each variable. */
|
||||
graph: VariableGraph;
|
||||
/** Direct parents of each variable. */
|
||||
parentGraph: VariableGraph;
|
||||
/** All transitive descendants of each variable (precomputed). */
|
||||
transitiveDescendants: VariableGraph;
|
||||
hasCycle: boolean;
|
||||
cycleNodes?: string[];
|
||||
}
|
||||
|
||||
/** Names of QUERY variables whose query references `variableName`. */
|
||||
function getDependents(
|
||||
variableName: string,
|
||||
variables: VariableFormModel[],
|
||||
): string[] {
|
||||
return variables
|
||||
.filter(
|
||||
(v) =>
|
||||
v.type === 'QUERY' &&
|
||||
!!v.name &&
|
||||
textContainsVariableReference(v.queryValue || '', variableName),
|
||||
)
|
||||
.map((v) => v.name);
|
||||
}
|
||||
|
||||
/** variable name → its direct dependents (children). */
|
||||
export function buildDependencies(
|
||||
variables: VariableFormModel[],
|
||||
): VariableGraph {
|
||||
const graph: VariableGraph = {};
|
||||
variables.forEach((v) => {
|
||||
if (v.name) {
|
||||
graph[v.name] = getDependents(v.name, variables);
|
||||
}
|
||||
});
|
||||
return graph;
|
||||
}
|
||||
|
||||
/** Invert a child graph into a parent graph. */
|
||||
export function buildParentGraph(graph: VariableGraph): VariableGraph {
|
||||
const parents: VariableGraph = {};
|
||||
Object.keys(graph).forEach((node) => {
|
||||
parents[node] = parents[node] ?? [];
|
||||
});
|
||||
Object.entries(graph).forEach(([node, children]) => {
|
||||
children.forEach((child) => {
|
||||
parents[child] = parents[child] ?? [];
|
||||
parents[child].push(node);
|
||||
});
|
||||
});
|
||||
return parents;
|
||||
}
|
||||
|
||||
function collectCyclePath(
|
||||
graph: VariableGraph,
|
||||
start: string,
|
||||
end: string,
|
||||
): string[] {
|
||||
const path: string[] = [];
|
||||
let current = start;
|
||||
const findParent = (node: string): string | undefined =>
|
||||
Object.keys(graph).find((key) => graph[key]?.includes(node));
|
||||
while (current !== end) {
|
||||
const parent = findParent(current);
|
||||
if (!parent) {
|
||||
break;
|
||||
}
|
||||
path.push(parent);
|
||||
current = parent;
|
||||
}
|
||||
return [start, ...path];
|
||||
}
|
||||
|
||||
function detectCycle(
|
||||
graph: VariableGraph,
|
||||
node: string,
|
||||
visited: Set<string>,
|
||||
recStack: Set<string>,
|
||||
): string[] | null {
|
||||
if (!visited.has(node)) {
|
||||
visited.add(node);
|
||||
recStack.add(node);
|
||||
let cycleNodes: string[] | null = null;
|
||||
(graph[node] || []).some((neighbor) => {
|
||||
if (!visited.has(neighbor)) {
|
||||
const found = detectCycle(graph, neighbor, visited, recStack);
|
||||
if (found) {
|
||||
cycleNodes = found;
|
||||
return true;
|
||||
}
|
||||
} else if (recStack.has(neighbor)) {
|
||||
cycleNodes = collectCyclePath(graph, node, neighbor);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (cycleNodes) {
|
||||
return cycleNodes;
|
||||
}
|
||||
}
|
||||
recStack.delete(node);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Build the full dependency data (topo order, parents, transitive descendants, cycle info). */
|
||||
export function buildDependencyData(
|
||||
dependencies: VariableGraph,
|
||||
): VariableDependencyData {
|
||||
const inDegree: Record<string, number> = {};
|
||||
const adjList: VariableGraph = {};
|
||||
|
||||
Object.keys(dependencies).forEach((node) => {
|
||||
inDegree[node] = inDegree[node] ?? 0;
|
||||
adjList[node] = adjList[node] ?? [];
|
||||
(dependencies[node] || []).forEach((child) => {
|
||||
inDegree[child] = inDegree[child] ?? 0;
|
||||
inDegree[child] += 1;
|
||||
adjList[node].push(child);
|
||||
});
|
||||
});
|
||||
|
||||
const visited = new Set<string>();
|
||||
const recStack = new Set<string>();
|
||||
let cycleNodes: string[] | undefined;
|
||||
Object.keys(dependencies).some((node) => {
|
||||
if (!visited.has(node)) {
|
||||
const found = detectCycle(dependencies, node, visited, recStack);
|
||||
if (found) {
|
||||
cycleNodes = found;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Topological sort (Kahn's algorithm).
|
||||
const queue = Object.keys(inDegree).filter((n) => inDegree[n] === 0);
|
||||
const order: string[] = [];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (current === undefined) {
|
||||
break;
|
||||
}
|
||||
order.push(current);
|
||||
(adjList[current] || []).forEach((neighbor) => {
|
||||
inDegree[neighbor] -= 1;
|
||||
if (inDegree[neighbor] === 0) {
|
||||
queue.push(neighbor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const hasCycle = order.length !== Object.keys(dependencies).length;
|
||||
|
||||
// Transitive descendants: walk topo order in reverse.
|
||||
const transitiveDescendants: VariableGraph = {};
|
||||
for (let i = order.length - 1; i >= 0; i--) {
|
||||
const node = order[i];
|
||||
const desc = new Set<string>();
|
||||
(adjList[node] || []).forEach((child) => {
|
||||
desc.add(child);
|
||||
(transitiveDescendants[child] || []).forEach((d) => desc.add(d));
|
||||
});
|
||||
transitiveDescendants[node] = Array.from(desc);
|
||||
}
|
||||
|
||||
return {
|
||||
order,
|
||||
graph: adjList,
|
||||
parentGraph: buildParentGraph(adjList),
|
||||
transitiveDescendants,
|
||||
hasCycle,
|
||||
cycleNodes,
|
||||
};
|
||||
}
|
||||
|
||||
/** Compute the full dependency data straight from the variable list. */
|
||||
export function computeVariableDependencies(
|
||||
variables: VariableFormModel[],
|
||||
): VariableDependencyData {
|
||||
return buildDependencyData(buildDependencies(variables));
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { useAppContext } from 'providers/App/App';
|
||||
import DashboardDescription from './DashboardDescription';
|
||||
import PanelsAndSectionsLayout from './PanelsAndSectionsLayout';
|
||||
import { useDashboardStore } from './store/useDashboardStore';
|
||||
import VariablesBar from './VariablesBar/VariablesBar';
|
||||
import styles from './DashboardContainer.module.scss';
|
||||
|
||||
interface DashboardContainerProps {
|
||||
@@ -46,7 +45,6 @@ function DashboardContainer({
|
||||
handle={fullScreenHandle}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<VariablesBar dashboard={dashboard} />
|
||||
<PanelsAndSectionsLayout layouts={layouts} panels={panels} />
|
||||
</div>
|
||||
{/* Shared panel-type picker (V1 component): opened from any "New Panel"
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import type { StateCreator } from 'zustand';
|
||||
|
||||
import type {
|
||||
VariableSelection,
|
||||
VariableSelectionMap,
|
||||
} from '../../VariablesBar/selectionTypes';
|
||||
import type { DashboardStore } from '../useDashboardStore';
|
||||
|
||||
/**
|
||||
* Runtime variable selection — the values the user picks in the variable bar.
|
||||
* Keyed by dashboardId → variable name. Frontend-only and persisted to
|
||||
* localStorage (mirrored to the URL by the bar for shareable links); it is
|
||||
* deliberately NOT part of the dashboard spec, so selecting a value never
|
||||
* patches the dashboard.
|
||||
*/
|
||||
export interface VariableSelectionSlice {
|
||||
variableValues: Record<string, VariableSelectionMap>;
|
||||
setVariableValue: (
|
||||
dashboardId: string,
|
||||
name: string,
|
||||
selection: VariableSelection,
|
||||
) => void;
|
||||
/** Bulk set (used to seed from URL/localStorage/defaults on load). */
|
||||
setVariableValues: (dashboardId: string, values: VariableSelectionMap) => void;
|
||||
}
|
||||
|
||||
export const createVariableSelectionSlice: StateCreator<
|
||||
DashboardStore,
|
||||
[['zustand/persist', unknown]],
|
||||
[],
|
||||
VariableSelectionSlice
|
||||
> = (set, get) => ({
|
||||
variableValues: {},
|
||||
setVariableValue: (dashboardId, name, selection): void => {
|
||||
const { variableValues } = get();
|
||||
set({
|
||||
variableValues: {
|
||||
...variableValues,
|
||||
[dashboardId]: { ...variableValues[dashboardId], [name]: selection },
|
||||
},
|
||||
});
|
||||
},
|
||||
setVariableValues: (dashboardId, values): void => {
|
||||
const { variableValues } = get();
|
||||
set({
|
||||
variableValues: { ...variableValues, [dashboardId]: values },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/** Selector: the selection map for a dashboard (empty if none). */
|
||||
export const selectVariableValues =
|
||||
(dashboardId: string) =>
|
||||
(state: DashboardStore): VariableSelectionMap =>
|
||||
state.variableValues[dashboardId] ?? {};
|
||||
@@ -9,36 +9,25 @@ import {
|
||||
createCollapseSlice,
|
||||
type CollapseSlice,
|
||||
} from './slices/collapseSlice';
|
||||
import {
|
||||
createVariableSelectionSlice,
|
||||
type VariableSelectionSlice,
|
||||
} from './slices/variableSelectionSlice';
|
||||
|
||||
export type DashboardStore = EditContextSlice &
|
||||
CollapseSlice &
|
||||
VariableSelectionSlice;
|
||||
export type DashboardStore = EditContextSlice & CollapseSlice;
|
||||
|
||||
/**
|
||||
* V2 dashboard session store. Holds cross-cutting client state only — never the
|
||||
* dashboard spec (that stays in react-query via useGetDashboardV2). Slices:
|
||||
* dashboard spec (that stays in react-query via useGetDashboardV2). Two slices:
|
||||
* - edit-context: dashboardId / isEditable / refetch (set once, not persisted).
|
||||
* - collapse: per-section open state (frontend-only, persisted to localStorage).
|
||||
* - variable-selection: runtime variable values (frontend-only, persisted).
|
||||
*/
|
||||
export const useDashboardStore = create<DashboardStore>()(
|
||||
persist(
|
||||
(...a) => ({
|
||||
...createEditContextSlice(...a),
|
||||
...createCollapseSlice(...a),
|
||||
...createVariableSelectionSlice(...a),
|
||||
}),
|
||||
{
|
||||
name: '@signoz/dashboard-v2',
|
||||
// Persist UI-only state (context incl. the refetch fn is transient).
|
||||
partialize: (state) => ({
|
||||
collapsed: state.collapsed,
|
||||
variableValues: state.variableValues,
|
||||
}),
|
||||
// Persist only the collapse map — context (incl. the refetch fn) is transient.
|
||||
partialize: (state) => ({ collapsed: state.collapsed }),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="f67d1585-6164-4ad0-b2dd-f9cc59b2969f" x1="9.908" y1="15.943" x2="7.516" y2="2.383" gradientUnits="userSpaceOnUse"><stop offset="0.15" stop-color="#0078d4"/><stop offset="0.8" stop-color="#5ea0ef"/><stop offset="1" stop-color="#83b9f9"/></linearGradient></defs><g id="a4fd1868-54fe-4ca6-8ff6-3b01866dc27b"><path d="M14.49,7.15A5.147,5.147,0,0,0,9.24,2.164,5.272,5.272,0,0,0,4.216,5.653,4.869,4.869,0,0,0,0,10.4a4.946,4.946,0,0,0,5.068,4.814H13.82A4.292,4.292,0,0,0,18,11.127,4.105,4.105,0,0,0,14.49,7.15Z" fill="url(#f67d1585-6164-4ad0-b2dd-f9cc59b2969f)"/><path d="M12.9,11.4V8H12v4.13h2.46V11.4ZM5.76,9.73a1.825,1.825,0,0,1-.51-.31.441.441,0,0,1-.12-.32.342.342,0,0,1,.15-.3.683.683,0,0,1,.42-.12,1.62,1.62,0,0,1,1,.29V8.11a2.58,2.58,0,0,0-1-.16,1.641,1.641,0,0,0-1.09.34,1.08,1.08,0,0,0-.42.89c0,.51.32.91,1,1.21a2.907,2.907,0,0,1,.62.36.419.419,0,0,1,.15.32.381.381,0,0,1-.16.31.806.806,0,0,1-.45.11,1.66,1.66,0,0,1-1.09-.42V12a2.173,2.173,0,0,0,1.07.24,1.877,1.877,0,0,0,1.18-.33A1.08,1.08,0,0,0,6.84,11a1.048,1.048,0,0,0-.25-.7A2.425,2.425,0,0,0,5.76,9.73ZM11,11.32A2.191,2.191,0,0,0,11,9a1.808,1.808,0,0,0-.7-.75,2,2,0,0,0-1-.26,2.112,2.112,0,0,0-1.08.27A1.856,1.856,0,0,0,7.49,9a2.465,2.465,0,0,0-.26,1.14,2.256,2.256,0,0,0,.24,1,1.766,1.766,0,0,0,.69.74,2.056,2.056,0,0,0,1,.3l.86,1h1.21L10,12.08A1.79,1.79,0,0,0,11,11.32Zm-1-.25a.941.941,0,0,1-.76.35.916.916,0,0,1-.76-.36,1.523,1.523,0,0,1-.29-1,1.529,1.529,0,0,1,.29-1,1,1,0,0,1,.78-.37.869.869,0,0,1,.75.37,1.619,1.619,0,0,1,.27,1A1.459,1.459,0,0,1,10,11.07Z" fill="#f2f2f2"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,565 @@
|
||||
{
|
||||
"id": "sqldatabase",
|
||||
"title": "Azure SQL Database",
|
||||
"icon": "file://icon.svg",
|
||||
"overview": "file://overview.md",
|
||||
"supportedSignals": {
|
||||
"metrics": true,
|
||||
"logs": true
|
||||
},
|
||||
"dataCollected": {
|
||||
"metrics": [
|
||||
{
|
||||
"name": "azure_cpu_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Percentage of CPU used by the database workload, relative to its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Percentage of CPU used by the database workload, relative to its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Percentage of CPU used by the database workload, relative to its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_cpu_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Total CPU usage (user plus system) of the SQL instance, as a percentage."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_cpu_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Total CPU usage (user plus system) of the SQL instance, as a percentage."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_cpu_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Total CPU usage (user plus system) of the SQL instance, as a percentage."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_memory_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Memory usage of the SQL instance, as a percentage of its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_memory_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Memory usage of the SQL instance, as a percentage of its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sql_instance_memory_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Memory usage of the SQL instance, as a percentage of its limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_physical_data_read_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data file IO usage as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_physical_data_read_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data file IO usage as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_physical_data_read_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data file IO usage as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_write_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Transaction log write throughput as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_write_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Transaction log write throughput as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_write_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Transaction log write throughput as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_workers_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Worker threads in use as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_workers_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Worker threads in use as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_workers_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Worker threads in use as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Active sessions as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Active sessions as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Active sessions as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_count_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of active sessions."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_count_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of active sessions."
|
||||
},
|
||||
{
|
||||
"name": "azure_sessions_count_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of active sessions."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_consumption_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "DTU consumption as a percentage of the limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_consumption_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "DTU consumption as a percentage of the limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_consumption_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "DTU consumption as a percentage of the limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_used_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTUs used (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_used_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTUs used (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_used_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTUs used (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_limit_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTU limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_limit_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTU limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_dtu_limit_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "DTU limit (DTU-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_used_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCores used (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_used_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCores used (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_used_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCores used (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_limit_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCore limit (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_limit_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCore limit (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_cpu_limit_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "vCore limit (vCore-based purchasing model)."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used by the database."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_maximum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used by the database."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_minimum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used by the database."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used as a percentage of the maximum data size."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used as a percentage of the maximum data size."
|
||||
},
|
||||
{
|
||||
"name": "azure_storage_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Data space used as a percentage of the maximum data size."
|
||||
},
|
||||
{
|
||||
"name": "azure_allocated_data_storage_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space allocated to the database (includes unused space)."
|
||||
},
|
||||
{
|
||||
"name": "azure_allocated_data_storage_maximum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space allocated to the database (includes unused space)."
|
||||
},
|
||||
{
|
||||
"name": "azure_allocated_data_storage_minimum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Data space allocated to the database (includes unused space)."
|
||||
},
|
||||
{
|
||||
"name": "azure_xtp_storage_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "In-Memory OLTP storage used as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_xtp_storage_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "In-Memory OLTP storage used as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_xtp_storage_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "In-Memory OLTP storage used as a percentage of the limit."
|
||||
},
|
||||
{
|
||||
"name": "azure_full_backup_size_bytes_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative full backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_full_backup_size_bytes_maximum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative full backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_full_backup_size_bytes_minimum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative full backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_diff_backup_size_bytes_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative differential backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_diff_backup_size_bytes_maximum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative differential backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_diff_backup_size_bytes_minimum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative differential backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_backup_size_bytes_average",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative transaction log backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_backup_size_bytes_maximum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative transaction log backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_log_backup_size_bytes_minimum",
|
||||
"unit": "Bytes",
|
||||
"type": "Gauge",
|
||||
"description": "Cumulative transaction log backup storage size."
|
||||
},
|
||||
{
|
||||
"name": "azure_replication_lag_seconds_average",
|
||||
"unit": "Seconds",
|
||||
"type": "Gauge",
|
||||
"description": "Geo-replication lag (RPO) in seconds; reported on the primary database only."
|
||||
},
|
||||
{
|
||||
"name": "azure_replication_lag_seconds_maximum",
|
||||
"unit": "Seconds",
|
||||
"type": "Gauge",
|
||||
"description": "Geo-replication lag (RPO) in seconds; reported on the primary database only."
|
||||
},
|
||||
{
|
||||
"name": "azure_replication_lag_seconds_minimum",
|
||||
"unit": "Seconds",
|
||||
"type": "Gauge",
|
||||
"description": "Geo-replication lag (RPO) in seconds; reported on the primary database only."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_successful_total",
|
||||
"unit": "Count",
|
||||
"type": "Sum",
|
||||
"description": "Number of successful connections."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_successful_count",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of successful connections."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_failed_total",
|
||||
"unit": "Count",
|
||||
"type": "Sum",
|
||||
"description": "Number of failed connections caused by system errors."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_failed_count",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of failed connections caused by system errors."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_failed_user_error_total",
|
||||
"unit": "Count",
|
||||
"type": "Sum",
|
||||
"description": "Number of failed connections caused by user errors."
|
||||
},
|
||||
{
|
||||
"name": "azure_connection_failed_user_error_count",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of failed connections caused by user errors."
|
||||
},
|
||||
{
|
||||
"name": "azure_blocked_by_firewall_total",
|
||||
"unit": "Count",
|
||||
"type": "Sum",
|
||||
"description": "Number of connection attempts blocked by the firewall."
|
||||
},
|
||||
{
|
||||
"name": "azure_blocked_by_firewall_count",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of connection attempts blocked by the firewall."
|
||||
},
|
||||
{
|
||||
"name": "azure_deadlock_total",
|
||||
"unit": "Count",
|
||||
"type": "Sum",
|
||||
"description": "Number of deadlocks."
|
||||
},
|
||||
{
|
||||
"name": "azure_deadlock_count",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Number of deadlocks."
|
||||
},
|
||||
{
|
||||
"name": "azure_availability_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Database availability percentage (100 if connections succeed, 0 if all fail) per minute."
|
||||
},
|
||||
{
|
||||
"name": "azure_availability_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Database availability percentage (100 if connections succeed, 0 if all fail) per minute."
|
||||
},
|
||||
{
|
||||
"name": "azure_availability_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Database availability percentage (100 if connections succeed, 0 if all fail) per minute."
|
||||
},
|
||||
{
|
||||
"name": "azure_availability_count",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "Database availability percentage (100 if connections succeed, 0 if all fail) per minute."
|
||||
},
|
||||
{
|
||||
"name": "azure_availability_total",
|
||||
"unit": "Percent",
|
||||
"type": "Sum",
|
||||
"description": "Database availability percentage (100 if connections succeed, 0 if all fail) per minute."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_data_size_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in tempdb data files, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_data_size_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in tempdb data files, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_data_size_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in tempdb data files, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_size_average",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in the tempdb transaction log file, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_size_maximum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in the tempdb transaction log file, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_size_minimum",
|
||||
"unit": "Count",
|
||||
"type": "Gauge",
|
||||
"description": "Space used in the tempdb transaction log file, in kilobytes."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_used_percent_average",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "tempdb transaction log space used as a percentage."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_used_percent_maximum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "tempdb transaction log space used as a percentage."
|
||||
},
|
||||
{
|
||||
"name": "azure_tempdb_log_used_percent_minimum",
|
||||
"unit": "Percent",
|
||||
"type": "Gauge",
|
||||
"description": "tempdb transaction log space used as a percentage."
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"name": "Resource ID",
|
||||
"path": "resources.azure.resource.id",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"telemetryCollectionStrategy": {
|
||||
"azure": {
|
||||
"resourceProvider": "Microsoft.Sql",
|
||||
"resourceType": "servers/databases",
|
||||
"metrics": {},
|
||||
"logs": {
|
||||
"categoryGroups": [
|
||||
"allLogs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"assets": {
|
||||
"dashboards": [
|
||||
{
|
||||
"id": "overview",
|
||||
"title": "Azure SQL Database Overview",
|
||||
"description": "Overview of Azure SQL Database metrics",
|
||||
"definition": "file://assets/dashboards/overview.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
### Monitor Azure SQL Database with SigNoz
|
||||
|
||||
Collect key Azure SQL Database (single database) metrics and view them with an out of the box dashboard.
|
||||
|
||||
This integration collects platform metrics for the `Microsoft.Sql/servers/databases` resource type.
|
||||
|
||||
Note: This integration is for Azure SQL Database (the PaaS offering). Azure SQL Managed Instance and SQL Server on Azure VMs expose a different set of metrics and are not covered here.
|
||||
@@ -31,6 +31,7 @@ var (
|
||||
AzureServiceAppService = ServiceID{valuer.NewString("appservice")}
|
||||
AzureServiceContainerApp = ServiceID{valuer.NewString("containerapp")}
|
||||
AzureServiceAKS = ServiceID{valuer.NewString("aks")}
|
||||
AzureServiceSQLDatabase = ServiceID{valuer.NewString("sqldatabase")}
|
||||
)
|
||||
|
||||
func (ServiceID) Enum() []any {
|
||||
@@ -54,6 +55,7 @@ func (ServiceID) Enum() []any {
|
||||
AzureServiceAppService,
|
||||
AzureServiceContainerApp,
|
||||
AzureServiceAKS,
|
||||
AzureServiceSQLDatabase,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ var SupportedServices = map[CloudProviderType][]ServiceID{
|
||||
AzureServiceAppService,
|
||||
AzureServiceContainerApp,
|
||||
AzureServiceAKS,
|
||||
AzureServiceSQLDatabase,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user