mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-17 06:32:10 +00:00
Compare commits
24 Commits
remote-dot
...
qb-json-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
540588086a | ||
|
|
aa39db8ac2 | ||
|
|
5a6cd4922c | ||
|
|
ab76e5634e | ||
|
|
6ab760b88c | ||
|
|
eb2c6b78c8 | ||
|
|
2d2d0c3d9f | ||
|
|
8a4544cbac | ||
|
|
8b3e7b2cf0 | ||
|
|
8898f02698 | ||
|
|
f277009ff8 | ||
|
|
17c6b79d79 | ||
|
|
76d6c23217 | ||
|
|
82dffdda56 | ||
|
|
8f38398863 | ||
|
|
eb39772d3c | ||
|
|
df72c897f9 | ||
|
|
4bbe5ead07 | ||
|
|
e36689ecba | ||
|
|
bac0e5f499 | ||
|
|
391e889f96 | ||
|
|
9254b879a9 | ||
|
|
ac62103228 | ||
|
|
491bf14bd0 |
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -43,6 +43,12 @@
|
||||
/pkg/analytics/ @vikrantgupta25
|
||||
/pkg/statsreporter/ @vikrantgupta25
|
||||
|
||||
# Emailing Owners
|
||||
|
||||
/pkg/emailing/ @vikrantgupta25
|
||||
/pkg/types/emailtypes/ @vikrantgupta25
|
||||
/templates/email/ @vikrantgupta25
|
||||
|
||||
# Querier Owners
|
||||
|
||||
/pkg/querier/ @srikanthccv
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -14,5 +14,8 @@
|
||||
},
|
||||
"[sql]": {
|
||||
"editor.defaultFormatter": "adpyke.vscode-sql-formatter"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "vscode.html-language-features"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,15 @@ emailing:
|
||||
templates:
|
||||
# The directory containing the email templates. This directory should contain a list of files defined at pkg/types/emailtypes/template.go.
|
||||
directory: /opt/signoz/conf/templates/email
|
||||
format:
|
||||
header:
|
||||
enabled: false
|
||||
logo_url: ""
|
||||
help:
|
||||
enabled: false
|
||||
email: ""
|
||||
footer:
|
||||
enabled: false
|
||||
smtp:
|
||||
# The SMTP server address.
|
||||
address: localhost:25
|
||||
|
||||
@@ -67,6 +67,14 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
featureSet[idx].Active = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ah.Respond(w, featureSet)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,3 +18,14 @@ func GetOrDefaultEnv(key string, fallback string) string {
|
||||
return v
|
||||
}
|
||||
|
||||
// constant functions that override env vars
|
||||
|
||||
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
|
||||
|
||||
var IsDotMetricsEnabled = false
|
||||
|
||||
func init() {
|
||||
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
|
||||
IsDotMetricsEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
"@signozhq/radio-group": "0.0.2",
|
||||
"@signozhq/resizable": "0.0.0",
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/table": "0.3.7",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sClustersListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -64,39 +64,41 @@ export const getK8sClustersList = async (
|
||||
props: K8sClustersListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/clusters/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sDaemonSetsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,39 +71,42 @@ export const getK8sDaemonSetsList = async (
|
||||
props: K8sDaemonSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
// filter prep (unchanged)…
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/daemonsets/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sDeploymentsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,39 +71,41 @@ export const getK8sDeploymentsList = async (
|
||||
props: K8sDeploymentsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/deployments/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sJobsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,39 +71,41 @@ export const getK8sJobsList = async (
|
||||
props: K8sJobsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/jobs/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sNamespacesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -62,39 +62,41 @@ export const getK8sNamespacesList = async (
|
||||
props: K8sNamespacesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/namespaces/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sNodesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -66,39 +66,41 @@ export const getK8sNodesList = async (
|
||||
props: K8sNodesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/nodes/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sPodsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -102,39 +102,41 @@ export const getK8sPodsList = async (
|
||||
props: K8sPodsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({
|
||||
...item,
|
||||
key: { ...item.key, key: mappedKey },
|
||||
});
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/pods/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sVolumesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -86,37 +86,39 @@ export const getK8sVolumesList = async (
|
||||
props: K8sVolumesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
// Prepare filters
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/pvcs/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
import { UnderscoreToDotMap } from '../utils';
|
||||
|
||||
export interface K8sStatefulSetsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -69,37 +69,39 @@ export const getK8sStatefulSetsList = async (
|
||||
props: K8sStatefulSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
// Prepare filters
|
||||
const requestProps = Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
const requestProps =
|
||||
dotMetricsEnabled && Array.isArray(props.filters?.items)
|
||||
? {
|
||||
...props,
|
||||
filters: {
|
||||
...props.filters,
|
||||
items: props.filters.items.reduce<typeof props.filters.items>(
|
||||
(acc, item) => {
|
||||
if (item.value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
if (
|
||||
item.key &&
|
||||
typeof item.key === 'object' &&
|
||||
'key' in item.key &&
|
||||
typeof item.key.key === 'string'
|
||||
) {
|
||||
const mappedKey = AttributeKeyMap[item.key.key] ?? item.key.key;
|
||||
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
|
||||
} else {
|
||||
acc.push(item);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
|
||||
const response = await axios.post('/statefulsets/list', requestProps, {
|
||||
signal,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const Logout = async (): Promise<void> => {
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
export const AttributeKeyMap: Record<string, string> = {
|
||||
export const UnderscoreToDotMap: Record<string, string> = {
|
||||
k8s_cluster_name: 'k8s.cluster.name',
|
||||
k8s_cluster_uid: 'k8s.cluster.uid',
|
||||
k8s_namespace_name: 'k8s.namespace.name',
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'time_series',
|
||||
data: { results: [timeSeries] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0, stepIntervals: {} },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('time_series', [
|
||||
@@ -156,7 +156,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'scalar',
|
||||
data: { results: [scalar] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0, stepIntervals: {} },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('scalar', [
|
||||
@@ -239,7 +239,7 @@ describe('convertV5ResponseToLegacy', () => {
|
||||
const v5Data: QueryRangeResponseV5 = {
|
||||
type: 'scalar',
|
||||
data: { results: [scalar] },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0 },
|
||||
meta: { rowsScanned: 0, bytesScanned: 0, durationMs: 0, stepIntervals: {} },
|
||||
};
|
||||
|
||||
const params = makeBaseParams('scalar', [
|
||||
|
||||
@@ -388,6 +388,7 @@ export function convertV5ResponseToLegacy(
|
||||
warnings: v5Data?.data?.warning || [],
|
||||
},
|
||||
warning: v5Data?.warning || undefined,
|
||||
meta: v5Data?.meta,
|
||||
},
|
||||
warning: v5Data?.warning || undefined,
|
||||
};
|
||||
@@ -406,6 +407,7 @@ export function convertV5ResponseToLegacy(
|
||||
payload: {
|
||||
data: convertedData,
|
||||
warning: v5Response.payload?.data?.warning || undefined,
|
||||
meta: v5Data?.meta,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
1
frontend/src/auto-import-registry.d.ts
vendored
1
frontend/src/auto-import-registry.d.ts
vendored
@@ -21,6 +21,7 @@ import '@signozhq/design-tokens';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/radio-group';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/sonner';
|
||||
import '@signozhq/table';
|
||||
|
||||
@@ -23,6 +23,9 @@ import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
|
||||
import './Metrics.styles.scss';
|
||||
|
||||
interface MetricsTabProps {
|
||||
@@ -47,6 +50,11 @@ function Metrics({
|
||||
handleTimeChange,
|
||||
isModalTimeSelection,
|
||||
}: MetricsTabProps): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const {
|
||||
visibilities,
|
||||
setElement,
|
||||
@@ -61,8 +69,14 @@ function Metrics({
|
||||
});
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getHostQueryPayload(hostName, timeRange.startTime, timeRange.endTime),
|
||||
[hostName, timeRange.startTime, timeRange.endTime],
|
||||
() =>
|
||||
getHostQueryPayload(
|
||||
hostName,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
[hostName, timeRange.startTime, timeRange.endTime, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -78,12 +78,10 @@ function TestWrapper({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
describe('VariableItem Integration Tests', () => {
|
||||
let user: ReturnType<typeof userEvent.setup>;
|
||||
let mockOnValueUpdate: jest.Mock;
|
||||
let mockSetVariablesToGetUpdated: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
user = userEvent.setup();
|
||||
mockOnValueUpdate = jest.fn();
|
||||
mockSetVariablesToGetUpdated = jest.fn();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -102,9 +100,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -150,9 +145,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -195,9 +187,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -247,9 +236,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -272,9 +258,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -308,9 +291,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -344,9 +324,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -369,9 +346,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -405,9 +379,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -461,9 +432,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -508,9 +476,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -548,9 +513,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
@@ -582,9 +544,6 @@ describe('VariableItem Integration Tests', () => {
|
||||
variableData={variable}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={null}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
@@ -282,11 +282,11 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
size="small"
|
||||
style={{ marginLeft: 'auto' }}
|
||||
checked={showIP ?? true}
|
||||
onClick={(): void => {
|
||||
onChange={(checked): void => {
|
||||
logEvent('API Monitoring: Show IP addresses clicked', {
|
||||
showIP: !(showIP ?? true),
|
||||
showIP: checked,
|
||||
});
|
||||
setParams({ showIP });
|
||||
setParams({ showIP: checked });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import {
|
||||
ApiMonitoringParams,
|
||||
useApiMonitoringParams,
|
||||
} from 'container/ApiMonitoring/queryParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import {
|
||||
otherFiltersResponse,
|
||||
@@ -18,10 +22,15 @@ import { QuickFiltersConfig } from './constants';
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: jest.fn(),
|
||||
}));
|
||||
jest.mock('container/ApiMonitoring/queryParams');
|
||||
|
||||
const handleFilterVisibilityChange = jest.fn();
|
||||
const redirectWithQueryBuilderData = jest.fn();
|
||||
const putHandler = jest.fn();
|
||||
const mockSetApiMonitoringParams = jest.fn() as jest.MockedFunction<
|
||||
(newParams: Partial<ApiMonitoringParams>, replace?: boolean) => void
|
||||
>;
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
@@ -84,6 +93,28 @@ TestQuickFilters.defaultProps = {
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
function TestQuickFiltersApiMonitoring({
|
||||
signal = SignalType.LOGS,
|
||||
config = QuickFiltersConfig,
|
||||
}: {
|
||||
signal?: SignalType;
|
||||
config?: IQuickFiltersConfig[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.API_MONITORING}
|
||||
config={config}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
signal={signal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TestQuickFiltersApiMonitoring.defaultProps = {
|
||||
signal: '',
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen();
|
||||
});
|
||||
@@ -112,6 +143,10 @@ beforeEach(() => {
|
||||
lastUsedQuery: 0,
|
||||
redirectWithQueryBuilderData,
|
||||
});
|
||||
mockUseApiMonitoringParams.mockReturnValue([
|
||||
{ showIP: true } as ApiMonitoringParams,
|
||||
mockSetApiMonitoringParams,
|
||||
]);
|
||||
setupServer();
|
||||
});
|
||||
|
||||
@@ -251,6 +286,24 @@ describe('Quick Filters', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
it('toggles Show IP addresses and updates API Monitoring params', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<TestQuickFiltersApiMonitoring />);
|
||||
|
||||
// Switch should be rendered and initially checked
|
||||
expect(screen.getByText('Show IP addresses')).toBeInTheDocument();
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
await user.click(toggle);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetApiMonitoringParams).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ showIP: false }),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quick Filters with custom filters', () => {
|
||||
|
||||
@@ -8,4 +8,5 @@ export enum FeatureKeys {
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
ONBOARDING_V3 = 'onboarding_v3',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Exception, PayloadProps } from 'types/api/errors/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { FilterDropdownExtendsProps } from './types';
|
||||
import {
|
||||
extractFilterValues,
|
||||
@@ -413,6 +415,11 @@ function AllErrors(): JSX.Element {
|
||||
},
|
||||
];
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const onChangeHandler: TableProps<Exception>['onChange'] = useCallback(
|
||||
(
|
||||
paginations: TablePaginationConfig,
|
||||
@@ -448,7 +455,7 @@ function AllErrors(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!isUndefined(errorCountResponse.data?.payload)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('Exception: List page visited', {
|
||||
|
||||
@@ -9,11 +9,15 @@ import {
|
||||
import useVariablesFromUrl from 'hooks/dashboard/useVariablesFromUrl';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { initializeDefaultVariables } from 'providers/Dashboard/initializeDefaultVariables';
|
||||
import {
|
||||
enqueueDescendantsOfVariable,
|
||||
enqueueFetchOfAllVariables,
|
||||
initializeVariableFetchStore,
|
||||
} from 'providers/Dashboard/store/variableFetchStore';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { onUpdateVariableNode } from './util';
|
||||
import VariableItem from './VariableItem';
|
||||
|
||||
import './DashboardVariableSelection.styles.scss';
|
||||
@@ -22,8 +26,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
const {
|
||||
setSelectedDashboard,
|
||||
updateLocalStorageDashboardVariables,
|
||||
variablesToGetUpdated,
|
||||
setVariablesToGetUpdated,
|
||||
} = useDashboard();
|
||||
|
||||
const { updateUrlVariable, getUrlVariables } = useVariablesFromUrl();
|
||||
@@ -55,11 +57,14 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
[dependencyData?.order],
|
||||
);
|
||||
|
||||
// Trigger refetch when dependency order changes or global time changes
|
||||
// Initialize fetch store then start a new fetch cycle.
|
||||
// Runs on dependency order changes, and time range changes.
|
||||
useEffect(() => {
|
||||
if (dependencyData?.order && dependencyData.order.length > 0) {
|
||||
setVariablesToGetUpdated(dependencyData?.order || []);
|
||||
}
|
||||
const allVariableNames = sortedVariablesArray
|
||||
.map((v) => v.name)
|
||||
.filter((name): name is string => !!name);
|
||||
initializeVariableFetchStore(allVariableNames);
|
||||
enqueueFetchOfAllVariables();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dependencyOrderKey, minTime, maxTime]);
|
||||
|
||||
@@ -121,29 +126,14 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
return prev;
|
||||
});
|
||||
|
||||
if (dependencyData) {
|
||||
const updatedVariables: string[] = [];
|
||||
onUpdateVariableNode(
|
||||
name,
|
||||
dependencyData.graph,
|
||||
dependencyData.order,
|
||||
(node) => updatedVariables.push(node),
|
||||
);
|
||||
setVariablesToGetUpdated((prev) => [
|
||||
...new Set([...prev, ...updatedVariables.filter((v) => v !== name)]),
|
||||
]);
|
||||
} else {
|
||||
setVariablesToGetUpdated((prev) => prev.filter((v) => v !== name));
|
||||
}
|
||||
// Cascade: enqueue query-type descendants for refetching
|
||||
enqueueDescendantsOfVariable(name);
|
||||
},
|
||||
[
|
||||
// This can be removed
|
||||
dashboardVariables,
|
||||
updateLocalStorageDashboardVariables,
|
||||
dependencyData,
|
||||
updateUrlVariable,
|
||||
setSelectedDashboard,
|
||||
setVariablesToGetUpdated,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -158,9 +148,6 @@ function DashboardVariableSelection(): JSX.Element | null {
|
||||
existingVariables={dashboardVariables}
|
||||
variableData={variable}
|
||||
onValueUpdate={onValueUpdate}
|
||||
variablesToGetUpdated={variablesToGetUpdated}
|
||||
setVariablesToGetUpdated={setVariablesToGetUpdated}
|
||||
dependencyData={dependencyData}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -2,18 +2,25 @@ import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getFieldValues } from 'api/dynamicVariables/getFieldValues';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useVariableFetchState } from 'hooks/dashboard/useVariableFetchState';
|
||||
import useDebounce from 'hooks/useDebounce';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { FieldValueResponse } from 'types/api/dynamicVariables/getFieldValues';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { isRetryableError as checkIfRetryableError } from 'utils/errorUtils';
|
||||
|
||||
import SelectVariableInput from './SelectVariableInput';
|
||||
import { useDashboardVariableSelectHelper } from './useDashboardVariableSelectHelper';
|
||||
import { getOptionsForDynamicVariable } from './util';
|
||||
import {
|
||||
buildExistingDynamicVariableQuery,
|
||||
extractErrorMessage,
|
||||
getOptionsForDynamicVariable,
|
||||
mergeUniqueStrings,
|
||||
settleVariableFetch,
|
||||
} from './util';
|
||||
import { VariableItemProps } from './VariableItem';
|
||||
import { dynamicVariableSelectStrategy } from './variableSelectStrategy/dynamicVariableSelectStrategy';
|
||||
|
||||
@@ -24,7 +31,6 @@ type DynamicVariableInputProps = Pick<
|
||||
'variableData' | 'onValueUpdate' | 'existingVariables'
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function DynamicVariableInput({
|
||||
variableData,
|
||||
onValueUpdate,
|
||||
@@ -55,14 +61,8 @@ function DynamicVariableInput({
|
||||
|
||||
const debouncedApiSearchText = useDebounce(apiSearchText, DEBOUNCE_DELAY);
|
||||
|
||||
// Build a memoized list of all currently available option strings (normalized + related)
|
||||
const allAvailableOptionStrings = useMemo(
|
||||
() => [
|
||||
...new Set([
|
||||
...optionsData.map((v) => v.toString()),
|
||||
...relatedValues.map((v) => v.toString()),
|
||||
]),
|
||||
],
|
||||
() => mergeUniqueStrings(optionsData, relatedValues),
|
||||
[optionsData, relatedValues],
|
||||
);
|
||||
|
||||
@@ -104,67 +104,24 @@ function DynamicVariableInput({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
// existing query is the query made from the other dynamic variables around this one with there current values
|
||||
// for e.g. k8s.namespace.name IN ["zeus", "gene"] AND doc_op_type IN ["test"]
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const existingQuery = useMemo(() => {
|
||||
if (!existingVariables || !variableData.dynamicVariablesAttribute) {
|
||||
return '';
|
||||
}
|
||||
const {
|
||||
variableFetchCycleId,
|
||||
isVariableSettled,
|
||||
isVariableFetching,
|
||||
hasVariableFetchedOnce,
|
||||
isVariableWaitingForDependencies,
|
||||
variableDependencyWaitMessage,
|
||||
} = useVariableFetchState(variableData.name || '');
|
||||
|
||||
const queryParts: string[] = [];
|
||||
|
||||
Object.entries(existingVariables).forEach(([, variable]) => {
|
||||
// Skip the current variable being processed
|
||||
if (variable.id === variableData.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only include dynamic variables that have selected values and are not selected as ALL
|
||||
if (
|
||||
variable.type === 'DYNAMIC' &&
|
||||
variable.dynamicVariablesAttribute &&
|
||||
variable.selectedValue &&
|
||||
!isEmpty(variable.selectedValue) &&
|
||||
(variable.showALLOption ? !variable.allSelected : true)
|
||||
) {
|
||||
const attribute = variable.dynamicVariablesAttribute;
|
||||
const values = Array.isArray(variable.selectedValue)
|
||||
? variable.selectedValue
|
||||
: [variable.selectedValue];
|
||||
|
||||
// Filter out empty values and convert to strings
|
||||
const validValues = values
|
||||
.filter((val) => val !== null && val !== undefined && val !== '')
|
||||
.map((val) => val.toString());
|
||||
|
||||
if (validValues.length > 0) {
|
||||
// Format values for query - wrap strings in quotes, keep numbers as is
|
||||
const formattedValues = validValues.map((val) => {
|
||||
// Check if value is a number
|
||||
const numValue = Number(val);
|
||||
if (!Number.isNaN(numValue) && Number.isFinite(numValue)) {
|
||||
return val; // Keep as number
|
||||
}
|
||||
// Escape single quotes and wrap in quotes
|
||||
return `'${val.replace(/'/g, "\\'")}'`;
|
||||
});
|
||||
|
||||
if (formattedValues.length === 1) {
|
||||
queryParts.push(`${attribute} = ${formattedValues[0]}`);
|
||||
} else {
|
||||
queryParts.push(`${attribute} IN [${formattedValues.join(', ')}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return queryParts.join(' AND ');
|
||||
}, [
|
||||
existingVariables,
|
||||
variableData.id,
|
||||
variableData.dynamicVariablesAttribute,
|
||||
]);
|
||||
const existingQuery = useMemo(
|
||||
() =>
|
||||
buildExistingDynamicVariableQuery(
|
||||
existingVariables,
|
||||
variableData.id,
|
||||
!!variableData.dynamicVariablesAttribute,
|
||||
),
|
||||
[existingVariables, variableData.id, variableData.dynamicVariablesAttribute],
|
||||
);
|
||||
|
||||
// Wrap the hook's onDropdownVisibleChange to also track isDropdownOpen and handle cleanup
|
||||
const handleSelectDropdownVisibilityChange = useCallback(
|
||||
@@ -182,6 +139,73 @@ function DynamicVariableInput({
|
||||
[onDropdownVisibleChange, optionsData, originalRelatedValues],
|
||||
);
|
||||
|
||||
const handleQuerySuccess = useCallback(
|
||||
(data: SuccessResponseV2<FieldValueResponse>): void => {
|
||||
const newNormalizedValues = data.data?.normalizedValues || [];
|
||||
const newRelatedValues = data.data?.relatedValues || [];
|
||||
|
||||
if (!debouncedApiSearchText) {
|
||||
setOptionsData(newNormalizedValues);
|
||||
setIsComplete(data.data?.complete || false);
|
||||
}
|
||||
setFilteredOptionsData(newNormalizedValues);
|
||||
setRelatedValues(newRelatedValues);
|
||||
setOriginalRelatedValues(newRelatedValues);
|
||||
|
||||
// Sync temp selection with latest API values when ALL is active and dropdown is open
|
||||
if (variableData.allSelected && isDropdownOpen) {
|
||||
const latestValues = mergeUniqueStrings(
|
||||
newNormalizedValues,
|
||||
newRelatedValues,
|
||||
);
|
||||
|
||||
const currentStrings = Array.isArray(tempSelection)
|
||||
? tempSelection.map((v) => v.toString())
|
||||
: tempSelection
|
||||
? [tempSelection.toString()]
|
||||
: [];
|
||||
|
||||
const areSame =
|
||||
currentStrings.length === latestValues.length &&
|
||||
latestValues.every((v) => currentStrings.includes(v));
|
||||
|
||||
if (!areSame) {
|
||||
setTempSelection(latestValues);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default if no value is selected (e.g., new variable, first load)
|
||||
if (!debouncedApiSearchText) {
|
||||
applyDefaultIfNeeded(
|
||||
mergeUniqueStrings(newNormalizedValues, newRelatedValues),
|
||||
);
|
||||
}
|
||||
|
||||
settleVariableFetch(variableData.name, 'complete');
|
||||
},
|
||||
[
|
||||
debouncedApiSearchText,
|
||||
variableData.allSelected,
|
||||
variableData.name,
|
||||
isDropdownOpen,
|
||||
tempSelection,
|
||||
setTempSelection,
|
||||
applyDefaultIfNeeded,
|
||||
],
|
||||
);
|
||||
|
||||
const handleQueryError = useCallback(
|
||||
(error: { message?: string } | null): void => {
|
||||
if (error) {
|
||||
setErrorMessage(extractErrorMessage(error));
|
||||
setIsRetryableError(checkIfRetryableError(error));
|
||||
}
|
||||
|
||||
settleVariableFetch(variableData.name, 'failure');
|
||||
},
|
||||
[variableData.name],
|
||||
);
|
||||
|
||||
const { isLoading, refetch } = useQuery(
|
||||
[
|
||||
REACT_QUERY_KEY.DASHBOARD_BY_ID,
|
||||
@@ -192,13 +216,22 @@ function DynamicVariableInput({
|
||||
debouncedApiSearchText,
|
||||
variableData.dynamicVariablesSource,
|
||||
variableData.dynamicVariablesAttribute,
|
||||
variableFetchCycleId,
|
||||
],
|
||||
{
|
||||
/*
|
||||
* enabled if
|
||||
* - we have dynamic variable source and attribute defined (ALWAYS)
|
||||
* - AND
|
||||
* - we're either still fetching variable options
|
||||
* - OR
|
||||
* - if variable is in idle state and we have already fetched options for it
|
||||
**/
|
||||
enabled:
|
||||
variableData.type === 'DYNAMIC' &&
|
||||
!!variableData.dynamicVariablesSource &&
|
||||
!!variableData.dynamicVariablesAttribute,
|
||||
queryFn: () =>
|
||||
!!variableData.dynamicVariablesAttribute &&
|
||||
(isVariableFetching || (isVariableSettled && hasVariableFetchedOnce)),
|
||||
queryFn: ({ signal }) =>
|
||||
getFieldValues(
|
||||
variableData.dynamicVariablesSource?.toLowerCase() === 'all telemetry'
|
||||
? undefined
|
||||
@@ -211,70 +244,10 @@ function DynamicVariableInput({
|
||||
minTime,
|
||||
maxTime,
|
||||
existingQuery,
|
||||
signal,
|
||||
),
|
||||
onSuccess: (data) => {
|
||||
const newNormalizedValues = data.data?.normalizedValues || [];
|
||||
const newRelatedValues = data.data?.relatedValues || [];
|
||||
|
||||
if (!debouncedApiSearchText) {
|
||||
setOptionsData(newNormalizedValues);
|
||||
setIsComplete(data.data?.complete || false);
|
||||
}
|
||||
setFilteredOptionsData(newNormalizedValues);
|
||||
setRelatedValues(newRelatedValues);
|
||||
setOriginalRelatedValues(newRelatedValues);
|
||||
|
||||
// Only run auto-check logic when necessary to avoid performance issues
|
||||
if (variableData.allSelected && isDropdownOpen) {
|
||||
// Build the latest full list from API (normalized + related)
|
||||
const latestValues = [
|
||||
...new Set([
|
||||
...newNormalizedValues.map((v) => v.toString()),
|
||||
...newRelatedValues.map((v) => v.toString()),
|
||||
]),
|
||||
];
|
||||
|
||||
// Update temp selection to exactly reflect latest API values when ALL is active
|
||||
const currentStrings = Array.isArray(tempSelection)
|
||||
? tempSelection.map((v) => v.toString())
|
||||
: tempSelection
|
||||
? [tempSelection.toString()]
|
||||
: [];
|
||||
const areSame =
|
||||
currentStrings.length === latestValues.length &&
|
||||
latestValues.every((v) => currentStrings.includes(v));
|
||||
if (!areSame) {
|
||||
setTempSelection(latestValues);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply default if no value is selected (e.g., new variable, first load)
|
||||
if (!debouncedApiSearchText) {
|
||||
const allNewOptions = [
|
||||
...new Set([
|
||||
...newNormalizedValues.map((v) => v.toString()),
|
||||
...newRelatedValues.map((v) => v.toString()),
|
||||
]),
|
||||
];
|
||||
applyDefaultIfNeeded(allNewOptions);
|
||||
}
|
||||
},
|
||||
onError: (error: any) => {
|
||||
if (error) {
|
||||
let message = SOMETHING_WENT_WRONG;
|
||||
if (error?.message) {
|
||||
message = error?.message;
|
||||
} else {
|
||||
message =
|
||||
'Please make sure configuration is valid and you have required setup and permissions';
|
||||
}
|
||||
setErrorMessage(message);
|
||||
|
||||
// Check if error is retryable (5xx) or not (4xx)
|
||||
const isRetryable = checkIfRetryableError(error);
|
||||
setIsRetryableError(isRetryable);
|
||||
}
|
||||
},
|
||||
onSuccess: handleQuerySuccess,
|
||||
onError: handleQueryError,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -336,6 +309,8 @@ function DynamicVariableInput({
|
||||
showRetryButton={isRetryableError}
|
||||
showIncompleteDataMessage={!isComplete && filteredOptionsData.length > 0}
|
||||
onSearch={handleSearch}
|
||||
waiting={isVariableWaitingForDependencies}
|
||||
waitingMessage={variableDependencyWaitMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useVariableFetchState } from 'hooks/dashboard/useVariableFetchState';
|
||||
import sortValues from 'lib/dashboardVariables/sortVariableValues';
|
||||
import { isArray, isString } from 'lodash-es';
|
||||
import { isArray, isEmpty, isString } from 'lodash-es';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { VariableResponseProps } from 'types/api/dashboard/variables/query';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@@ -12,26 +13,18 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { variablePropsToPayloadVariables } from '../utils';
|
||||
import SelectVariableInput from './SelectVariableInput';
|
||||
import { useDashboardVariableSelectHelper } from './useDashboardVariableSelectHelper';
|
||||
import { areArraysEqual, checkAPIInvocation } from './util';
|
||||
import { areArraysEqual, settleVariableFetch } from './util';
|
||||
import { VariableItemProps } from './VariableItem';
|
||||
import { queryVariableSelectStrategy } from './variableSelectStrategy/queryVariableSelectStrategy';
|
||||
|
||||
type QueryVariableInputProps = Pick<
|
||||
VariableItemProps,
|
||||
| 'variableData'
|
||||
| 'existingVariables'
|
||||
| 'onValueUpdate'
|
||||
| 'variablesToGetUpdated'
|
||||
| 'setVariablesToGetUpdated'
|
||||
| 'dependencyData'
|
||||
'variableData' | 'existingVariables' | 'onValueUpdate'
|
||||
>;
|
||||
|
||||
function QueryVariableInput({
|
||||
variableData,
|
||||
existingVariables,
|
||||
variablesToGetUpdated,
|
||||
setVariablesToGetUpdated,
|
||||
dependencyData,
|
||||
onValueUpdate,
|
||||
}: QueryVariableInputProps): JSX.Element {
|
||||
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
|
||||
@@ -43,6 +36,15 @@ function QueryVariableInput({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const {
|
||||
variableFetchCycleId,
|
||||
isVariableSettled,
|
||||
isVariableFetching,
|
||||
hasVariableFetchedOnce,
|
||||
isVariableWaitingForDependencies,
|
||||
variableDependencyWaitMessage,
|
||||
} = useVariableFetchState(variableData.name || '');
|
||||
|
||||
const {
|
||||
tempSelection,
|
||||
setTempSelection,
|
||||
@@ -60,16 +62,6 @@ function QueryVariableInput({
|
||||
strategy: queryVariableSelectStrategy,
|
||||
});
|
||||
|
||||
const validVariableUpdate = useCallback((): boolean => {
|
||||
if (!variableData.name) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(
|
||||
variablesToGetUpdated.length &&
|
||||
variablesToGetUpdated[0] === variableData.name,
|
||||
);
|
||||
}, [variableData.name, variablesToGetUpdated]);
|
||||
|
||||
const getOptions = useCallback(
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
(variablesRes: VariableResponseProps | null): void => {
|
||||
@@ -103,18 +95,24 @@ function QueryVariableInput({
|
||||
valueNotInList = true;
|
||||
}
|
||||
|
||||
// variablesData.allSelected is added for the case where on change of options we need to update the
|
||||
// local storage
|
||||
if (
|
||||
variableData.name &&
|
||||
(validVariableUpdate() || valueNotInList || variableData.allSelected)
|
||||
) {
|
||||
if (variableData.name && (valueNotInList || variableData.allSelected)) {
|
||||
if (
|
||||
variableData.allSelected &&
|
||||
variableData.multiSelect &&
|
||||
variableData.showALLOption
|
||||
) {
|
||||
onValueUpdate(variableData.name, variableData.id, newOptionsData, true);
|
||||
if (
|
||||
variableData.name &&
|
||||
variableData.id &&
|
||||
!isEmpty(variableData.selectedValue)
|
||||
) {
|
||||
onValueUpdate(
|
||||
variableData.name,
|
||||
variableData.id,
|
||||
newOptionsData,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
// Update tempSelection to maintain ALL state when dropdown is open
|
||||
if (tempSelection !== undefined) {
|
||||
@@ -132,7 +130,11 @@ function QueryVariableInput({
|
||||
newOptionsData.every((option) => selectedValue.includes(option));
|
||||
}
|
||||
|
||||
if (variableData.name && variableData.id) {
|
||||
if (
|
||||
variableData.name &&
|
||||
variableData.id &&
|
||||
!isEmpty(variableData.selectedValue)
|
||||
) {
|
||||
onValueUpdate(variableData.name, variableData.id, value, allSelected);
|
||||
}
|
||||
}
|
||||
@@ -141,10 +143,6 @@ function QueryVariableInput({
|
||||
setOptionsData(newOptionsData);
|
||||
// Apply default if no value is selected (e.g., new variable, first load)
|
||||
applyDefaultIfNeeded(newOptionsData);
|
||||
} else {
|
||||
setVariablesToGetUpdated((prev) =>
|
||||
prev.filter((name) => name !== variableData.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -157,8 +155,6 @@ function QueryVariableInput({
|
||||
onValueUpdate,
|
||||
tempSelection,
|
||||
setTempSelection,
|
||||
validVariableUpdate,
|
||||
setVariablesToGetUpdated,
|
||||
applyDefaultIfNeeded,
|
||||
],
|
||||
);
|
||||
@@ -169,27 +165,28 @@ function QueryVariableInput({
|
||||
variableData.name || '',
|
||||
`${minTime}`,
|
||||
`${maxTime}`,
|
||||
JSON.stringify(dependencyData?.order),
|
||||
variableFetchCycleId,
|
||||
],
|
||||
{
|
||||
enabled:
|
||||
variableData &&
|
||||
checkAPIInvocation(
|
||||
variablesToGetUpdated,
|
||||
variableData,
|
||||
dependencyData?.parentDependencyGraph,
|
||||
/*
|
||||
* enabled if
|
||||
* - we're either still fetching variable options
|
||||
* - OR
|
||||
* - if variable is in idle state and we have already fetched options for it
|
||||
**/
|
||||
enabled: isVariableFetching || (isVariableSettled && hasVariableFetchedOnce),
|
||||
queryFn: ({ signal }) =>
|
||||
dashboardVariablesQuery(
|
||||
{
|
||||
query: variableData.queryValue || '',
|
||||
variables: variablePropsToPayloadVariables(existingVariables),
|
||||
},
|
||||
signal,
|
||||
),
|
||||
queryFn: () =>
|
||||
dashboardVariablesQuery({
|
||||
query: variableData.queryValue || '',
|
||||
variables: variablePropsToPayloadVariables(existingVariables),
|
||||
}),
|
||||
refetchOnWindowFocus: false,
|
||||
onSuccess: (response) => {
|
||||
getOptions(response.payload);
|
||||
setVariablesToGetUpdated((prev) =>
|
||||
prev.filter((v) => v !== variableData.name),
|
||||
);
|
||||
settleVariableFetch(variableData.name, 'complete');
|
||||
},
|
||||
onError: (error: {
|
||||
details: {
|
||||
@@ -206,9 +203,7 @@ function QueryVariableInput({
|
||||
}
|
||||
setErrorMessage(message);
|
||||
}
|
||||
setVariablesToGetUpdated((prev) =>
|
||||
prev.filter((v) => v !== variableData.name),
|
||||
);
|
||||
settleVariableFetch(variableData.name, 'failure');
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -242,6 +237,8 @@ function QueryVariableInput({
|
||||
loading={isLoading}
|
||||
errorMessage={errorMessage}
|
||||
onRetry={handleRetry}
|
||||
waiting={isVariableWaitingForDependencies}
|
||||
waitingMessage={variableDependencyWaitMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ interface SelectVariableInputProps {
|
||||
showRetryButton?: boolean;
|
||||
showIncompleteDataMessage?: boolean;
|
||||
onSearch?: (searchTerm: string) => void;
|
||||
waiting?: boolean;
|
||||
waitingMessage?: string;
|
||||
}
|
||||
|
||||
const MAX_TAG_DISPLAY_VALUES = 10;
|
||||
@@ -65,6 +67,7 @@ function SelectVariableInput({
|
||||
showRetryButton,
|
||||
showIncompleteDataMessage,
|
||||
onSearch,
|
||||
waitingMessage,
|
||||
}: SelectVariableInputProps): JSX.Element {
|
||||
const commonProps = useMemo(
|
||||
() => ({
|
||||
@@ -78,7 +81,6 @@ function SelectVariableInput({
|
||||
className: 'variable-select',
|
||||
popupClassName: 'dropdown-styles',
|
||||
getPopupContainer: popupContainer,
|
||||
style: SelectItemStyle,
|
||||
showSearch: true,
|
||||
bordered: false,
|
||||
|
||||
@@ -86,6 +88,8 @@ function SelectVariableInput({
|
||||
'data-testid': 'variable-select',
|
||||
onChange,
|
||||
loading,
|
||||
waitingMessage,
|
||||
style: SelectItemStyle,
|
||||
options,
|
||||
errorMessage,
|
||||
onRetry,
|
||||
@@ -101,6 +105,7 @@ function SelectVariableInput({
|
||||
defaultValue,
|
||||
onChange,
|
||||
loading,
|
||||
waitingMessage,
|
||||
options,
|
||||
value,
|
||||
errorMessage,
|
||||
|
||||
@@ -47,14 +47,6 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -69,14 +61,6 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -92,14 +76,6 @@ describe('VariableItem', () => {
|
||||
variableData={mockVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -133,14 +109,6 @@ describe('VariableItem', () => {
|
||||
variableData={mockCustomVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -163,14 +131,6 @@ describe('VariableItem', () => {
|
||||
variableData={customVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
@@ -185,14 +145,6 @@ describe('VariableItem', () => {
|
||||
variableData={mockCustomVariableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={(): void => {}}
|
||||
dependencyData={{
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
}}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import { IDependencyData } from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import CustomVariableInput from './CustomVariableInput';
|
||||
@@ -21,18 +20,12 @@ export interface VariableItemProps {
|
||||
allSelected: boolean,
|
||||
haveCustomValuesSelected?: boolean,
|
||||
) => void;
|
||||
variablesToGetUpdated: string[];
|
||||
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
dependencyData: IDependencyData | null;
|
||||
}
|
||||
|
||||
function VariableItem({
|
||||
variableData,
|
||||
onValueUpdate,
|
||||
existingVariables,
|
||||
variablesToGetUpdated,
|
||||
setVariablesToGetUpdated,
|
||||
dependencyData,
|
||||
}: VariableItemProps): JSX.Element {
|
||||
const { name, description, type: variableType } = variableData;
|
||||
|
||||
@@ -65,9 +58,6 @@ function VariableItem({
|
||||
variableData={variableData}
|
||||
onValueUpdate={onValueUpdate}
|
||||
existingVariables={existingVariables}
|
||||
variablesToGetUpdated={variablesToGetUpdated}
|
||||
setVariablesToGetUpdated={setVariablesToGetUpdated}
|
||||
dependencyData={dependencyData}
|
||||
/>
|
||||
)}
|
||||
{variableType === 'DYNAMIC' && (
|
||||
|
||||
@@ -7,6 +7,19 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import DynamicVariableInput from '../DynamicVariableInput';
|
||||
|
||||
// Mock useVariableFetchState to return "fetching" state so useQuery is enabled
|
||||
jest.mock('hooks/dashboard/useVariableFetchState', () => ({
|
||||
useVariableFetchState: (): Record<string, unknown> => ({
|
||||
variableFetchCycleId: 0,
|
||||
variableFetchState: 'loading',
|
||||
isVariableSettled: false,
|
||||
isVariableFetching: true,
|
||||
hasVariableFetchedOnce: false,
|
||||
isVariableWaitingForDependencies: false,
|
||||
variableDependencyWaitMessage: '',
|
||||
}),
|
||||
}));
|
||||
|
||||
// Don't mock the components - use real ones
|
||||
|
||||
// Mock for useQuery
|
||||
@@ -217,9 +230,10 @@ describe('DynamicVariableInput Component', () => {
|
||||
'',
|
||||
'Traces',
|
||||
'service.name',
|
||||
0, // variableFetchCycleId
|
||||
],
|
||||
expect.objectContaining({
|
||||
enabled: true, // Type is 'DYNAMIC'
|
||||
enabled: true, // isVariableFetching is true from mock
|
||||
queryFn: expect.any(Function),
|
||||
onSuccess: expect.any(Function),
|
||||
onError: expect.any(Function),
|
||||
|
||||
@@ -8,14 +8,6 @@ import '@testing-library/jest-dom/extend-expect';
|
||||
import VariableItem from '../VariableItem';
|
||||
|
||||
const mockOnValueUpdate = jest.fn();
|
||||
const mockSetVariablesToGetUpdated = jest.fn();
|
||||
|
||||
const baseDependencyData = {
|
||||
order: [],
|
||||
graph: {},
|
||||
parentDependencyGraph: {},
|
||||
hasCycle: false,
|
||||
};
|
||||
|
||||
const TEST_VARIABLE_ID = 'test_variable';
|
||||
const VARIABLE_SELECT_TESTID = 'variable-select';
|
||||
@@ -31,9 +23,6 @@ const renderVariableItem = (
|
||||
variableData={variableData}
|
||||
existingVariables={{}}
|
||||
onValueUpdate={mockOnValueUpdate}
|
||||
variablesToGetUpdated={[]}
|
||||
setVariablesToGetUpdated={mockSetVariablesToGetUpdated}
|
||||
dependencyData={baseDependencyData}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
@@ -2,14 +2,12 @@ import {
|
||||
buildDependencies,
|
||||
buildDependencyGraph,
|
||||
buildParentDependencyGraph,
|
||||
checkAPIInvocation,
|
||||
onUpdateVariableNode,
|
||||
VariableGraph,
|
||||
} from '../util';
|
||||
import {
|
||||
buildDependenciesMock,
|
||||
buildGraphMock,
|
||||
checkAPIInvocationMock,
|
||||
onUpdateVariableNodeMock,
|
||||
} from './mock';
|
||||
|
||||
@@ -72,97 +70,6 @@ describe('dashboardVariables - utilities and processors', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkAPIInvocation', () => {
|
||||
const {
|
||||
variablesToGetUpdated,
|
||||
variableData,
|
||||
parentDependencyGraph,
|
||||
} = checkAPIInvocationMock;
|
||||
|
||||
const mockRootElement = {
|
||||
name: 'deployment_environment',
|
||||
key: '036a47cd-9ffc-47de-9f27-0329198964a8',
|
||||
id: '036a47cd-9ffc-47de-9f27-0329198964a8',
|
||||
modificationUUID: '5f71b591-f583-497c-839d-6a1590c3f60f',
|
||||
selectedValue: 'production',
|
||||
type: 'QUERY',
|
||||
// ... other properties omitted for brevity
|
||||
} as any;
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return false when variableData is empty', () => {
|
||||
expect(
|
||||
checkAPIInvocation(
|
||||
variablesToGetUpdated,
|
||||
variableData,
|
||||
parentDependencyGraph,
|
||||
),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when parentDependencyGraph is empty', () => {
|
||||
expect(
|
||||
checkAPIInvocation(variablesToGetUpdated, variableData, {}),
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('variable sequences', () => {
|
||||
it('should return true for valid sequence', () => {
|
||||
expect(
|
||||
checkAPIInvocation(
|
||||
['k8s_node_name', 'k8s_namespace_name'],
|
||||
variableData,
|
||||
parentDependencyGraph,
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false for invalid sequence', () => {
|
||||
expect(
|
||||
checkAPIInvocation(
|
||||
['k8s_cluster_name', 'k8s_node_name', 'k8s_namespace_name'],
|
||||
variableData,
|
||||
parentDependencyGraph,
|
||||
),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false when variableData is not in sequence', () => {
|
||||
expect(
|
||||
checkAPIInvocation(
|
||||
['deployment_environment', 'service_name', 'endpoint'],
|
||||
variableData,
|
||||
parentDependencyGraph,
|
||||
),
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('root element behavior', () => {
|
||||
it('should return true for valid root element sequence', () => {
|
||||
expect(
|
||||
checkAPIInvocation(
|
||||
[
|
||||
'deployment_environment',
|
||||
'service_name',
|
||||
'endpoint',
|
||||
'http_status_code',
|
||||
],
|
||||
mockRootElement,
|
||||
parentDependencyGraph,
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true for empty variablesToGetUpdated array', () => {
|
||||
expect(
|
||||
checkAPIInvocation([], mockRootElement, parentDependencyGraph),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Graph Building Utilities', () => {
|
||||
const { graph } = buildGraphMock;
|
||||
const { variables } = buildDependenciesMock;
|
||||
@@ -223,10 +130,86 @@ describe('dashboardVariables - utilities and processors', () => {
|
||||
},
|
||||
hasCycle: false,
|
||||
cycleNodes: undefined,
|
||||
transitiveDescendants: {
|
||||
deployment_environment: ['service_name', 'endpoint', 'http_status_code'],
|
||||
endpoint: ['http_status_code'],
|
||||
environment: [],
|
||||
http_status_code: [],
|
||||
k8s_cluster_name: ['k8s_node_name', 'k8s_namespace_name'],
|
||||
k8s_namespace_name: [],
|
||||
k8s_node_name: ['k8s_namespace_name'],
|
||||
service_name: ['endpoint', 'http_status_code'],
|
||||
},
|
||||
};
|
||||
|
||||
expect(buildDependencyGraph(graph)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return empty transitiveDescendants for an empty graph', () => {
|
||||
const result = buildDependencyGraph({});
|
||||
expect(result.transitiveDescendants).toEqual({});
|
||||
expect(result.order).toEqual([]);
|
||||
expect(result.hasCycle).toBe(false);
|
||||
});
|
||||
|
||||
it('should compute transitiveDescendants for a linear chain (a -> b -> c)', () => {
|
||||
const linearGraph: VariableGraph = {
|
||||
a: ['b'],
|
||||
b: ['c'],
|
||||
c: [],
|
||||
};
|
||||
const result = buildDependencyGraph(linearGraph);
|
||||
expect(result.transitiveDescendants).toEqual({
|
||||
a: ['b', 'c'],
|
||||
b: ['c'],
|
||||
c: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should compute transitiveDescendants for a diamond dependency (a -> b, a -> c, b -> d, c -> d)', () => {
|
||||
const diamondGraph: VariableGraph = {
|
||||
a: ['b', 'c'],
|
||||
b: ['d'],
|
||||
c: ['d'],
|
||||
d: [],
|
||||
};
|
||||
const result = buildDependencyGraph(diamondGraph);
|
||||
expect(result.transitiveDescendants.a).toEqual(
|
||||
expect.arrayContaining(['b', 'c', 'd']),
|
||||
);
|
||||
expect(result.transitiveDescendants.a).toHaveLength(3);
|
||||
expect(result.transitiveDescendants.b).toEqual(['d']);
|
||||
expect(result.transitiveDescendants.c).toEqual(['d']);
|
||||
expect(result.transitiveDescendants.d).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle disconnected components in transitiveDescendants', () => {
|
||||
const disconnectedGraph: VariableGraph = {
|
||||
a: ['b'],
|
||||
b: [],
|
||||
x: ['y'],
|
||||
y: [],
|
||||
};
|
||||
const result = buildDependencyGraph(disconnectedGraph);
|
||||
expect(result.transitiveDescendants.a).toEqual(['b']);
|
||||
expect(result.transitiveDescendants.b).toEqual([]);
|
||||
expect(result.transitiveDescendants.x).toEqual(['y']);
|
||||
expect(result.transitiveDescendants.y).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty transitiveDescendants for all leaf nodes', () => {
|
||||
const leafOnlyGraph: VariableGraph = {
|
||||
a: [],
|
||||
b: [],
|
||||
c: [],
|
||||
};
|
||||
const result = buildDependencyGraph(leafOnlyGraph);
|
||||
expect(result.transitiveDescendants).toEqual({
|
||||
a: [],
|
||||
b: [],
|
||||
c: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildDependencies', () => {
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
export const checkAPIInvocationMock = {
|
||||
variablesToGetUpdated: [],
|
||||
variableData: {
|
||||
name: 'k8s_node_name',
|
||||
key: '4d71d385-beaf-4434-8dbf-c62be68049fc',
|
||||
allSelected: false,
|
||||
customValue: '',
|
||||
description: '',
|
||||
id: '4d71d385-beaf-4434-8dbf-c62be68049fc',
|
||||
modificationUUID: '77233d3c-96d7-4ccb-aa9d-11b04d563068',
|
||||
multiSelect: false,
|
||||
order: 6,
|
||||
queryValue:
|
||||
"SELECT JSONExtractString(labels, 'k8s_node_name') AS k8s_node_name\nFROM signoz_metrics.distributed_time_series_v4_1day\nWHERE metric_name = 'k8s_node_cpu_time' AND JSONExtractString(labels, 'k8s_cluster_name') = {{.k8s_cluster_name}}\nGROUP BY k8s_node_name",
|
||||
selectedValue: 'gke-signoz-saas-si-consumer-bsc-e2sd4-a6d430fa-gvm2',
|
||||
showALLOption: false,
|
||||
sort: 'DISABLED',
|
||||
textboxValue: '',
|
||||
type: 'QUERY',
|
||||
},
|
||||
parentDependencyGraph: {
|
||||
deployment_environment: [],
|
||||
service_name: ['deployment_environment'],
|
||||
endpoint: ['deployment_environment', 'service_name'],
|
||||
http_status_code: ['endpoint'],
|
||||
k8s_cluster_name: [],
|
||||
environment: [],
|
||||
k8s_node_name: ['k8s_cluster_name'],
|
||||
k8s_namespace_name: ['k8s_cluster_name', 'k8s_node_name'],
|
||||
},
|
||||
} as any;
|
||||
|
||||
export const onUpdateVariableNodeMock = {
|
||||
nodeToUpdate: 'deployment_environment',
|
||||
graph: {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { OptionData } from 'components/NewSelect/types';
|
||||
import { isEmpty, isNull } from 'lodash-es';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { textContainsVariableReference } from 'lib/dashboardVariables/variableReference';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import {
|
||||
IDashboardVariables,
|
||||
IDependencyData,
|
||||
} from 'providers/Dashboard/store/dashboardVariables/dashboardVariablesStoreTypes';
|
||||
import {
|
||||
onVariableFetchComplete,
|
||||
onVariableFetchFailure,
|
||||
variableFetchStore,
|
||||
} from 'providers/Dashboard/store/variableFetchStore';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
export function areArraysEqual(
|
||||
@@ -45,30 +52,16 @@ const getDependentVariablesBasedOnVariableName = (
|
||||
}
|
||||
|
||||
return variables
|
||||
?.map((variable: any) => {
|
||||
.map((variable) => {
|
||||
if (variable.type === 'QUERY') {
|
||||
// Combined pattern for all formats
|
||||
// {{.variable_name}} - original format
|
||||
// $variable_name - dollar prefix format
|
||||
// [[variable_name]] - square bracket format
|
||||
// {{variable_name}} - without dot format
|
||||
const patterns = [
|
||||
`\\{\\{\\s*?\\.${variableName}\\s*?\\}\\}`, // {{.var}}
|
||||
`\\{\\{\\s*${variableName}\\s*\\}\\}`, // {{var}}
|
||||
`\\$${variableName}\\b`, // $var
|
||||
`\\[\\[\\s*${variableName}\\s*\\]\\]`, // [[var]]
|
||||
];
|
||||
const combinedRegex = new RegExp(patterns.join('|'));
|
||||
|
||||
const queryValue = variable.queryValue || '';
|
||||
const dependVarReMatch = queryValue.match(combinedRegex);
|
||||
if (dependVarReMatch !== null && dependVarReMatch.length > 0) {
|
||||
if (textContainsVariableReference(queryValue, variableName)) {
|
||||
return variable.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((val: string | null) => !isNull(val));
|
||||
.filter((val): val is string => val !== null);
|
||||
};
|
||||
export type VariableGraph = Record<string, string[]>;
|
||||
|
||||
@@ -246,10 +239,26 @@ export const buildDependencyGraph = (
|
||||
|
||||
const hasCycle = topologicalOrder.length !== Object.keys(dependencies)?.length;
|
||||
|
||||
// Pre-compute transitive descendants by walking topological order in reverse.
|
||||
// Each node's transitive descendants = direct children + their transitive descendants.
|
||||
const transitiveDescendants: VariableGraph = {};
|
||||
for (let i = topologicalOrder.length - 1; i >= 0; i--) {
|
||||
const node = topologicalOrder[i];
|
||||
const desc = new Set<string>();
|
||||
for (const child of adjList[node] || []) {
|
||||
desc.add(child);
|
||||
for (const d of transitiveDescendants[child] || []) {
|
||||
desc.add(d);
|
||||
}
|
||||
}
|
||||
transitiveDescendants[node] = Array.from(desc);
|
||||
}
|
||||
|
||||
return {
|
||||
order: topologicalOrder,
|
||||
graph: adjList,
|
||||
parentDependencyGraph: buildParentDependencyGraph(adjList),
|
||||
transitiveDescendants,
|
||||
hasCycle,
|
||||
cycleNodes,
|
||||
};
|
||||
@@ -284,33 +293,6 @@ export const onUpdateVariableNode = (
|
||||
});
|
||||
};
|
||||
|
||||
export const checkAPIInvocation = (
|
||||
variablesToGetUpdated: string[],
|
||||
variableData: IDashboardVariable,
|
||||
parentDependencyGraph?: VariableGraph,
|
||||
): boolean => {
|
||||
if (isEmpty(variableData.name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isEmpty(parentDependencyGraph)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if no dependency then true
|
||||
const haveDependency =
|
||||
parentDependencyGraph?.[variableData.name || '']?.length > 0;
|
||||
if (!haveDependency) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if variable is in the list and has dependency then check if its the top element in the queue then true else false
|
||||
return (
|
||||
variablesToGetUpdated.length > 0 &&
|
||||
variablesToGetUpdated[0] === variableData.name
|
||||
);
|
||||
};
|
||||
|
||||
export const getOptionsForDynamicVariable = (
|
||||
normalizedValues: (string | number | boolean)[],
|
||||
relatedValues: string[],
|
||||
@@ -375,3 +357,130 @@ export const getSelectValue = (
|
||||
}
|
||||
return selectedValue?.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges multiple arrays of values into a single deduplicated string array.
|
||||
*/
|
||||
export function mergeUniqueStrings(
|
||||
...arrays: (string | number | boolean)[][]
|
||||
): string[] {
|
||||
return [...new Set(arrays.flatMap((arr) => arr.map((v) => v.toString())))];
|
||||
}
|
||||
|
||||
function isEligibleFilterVariable(
|
||||
variable: IDashboardVariable,
|
||||
currentVariableId: string,
|
||||
): boolean {
|
||||
if (variable.id === currentVariableId) {
|
||||
return false;
|
||||
}
|
||||
if (variable.type !== 'DYNAMIC') {
|
||||
return false;
|
||||
}
|
||||
if (!variable.dynamicVariablesAttribute) {
|
||||
return false;
|
||||
}
|
||||
if (!variable.selectedValue || isEmpty(variable.selectedValue)) {
|
||||
return false;
|
||||
}
|
||||
return !(variable.showALLOption && variable.allSelected);
|
||||
}
|
||||
|
||||
function formatQueryValue(val: string): string {
|
||||
const numValue = Number(val);
|
||||
if (!Number.isNaN(numValue) && Number.isFinite(numValue)) {
|
||||
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 query string from sibling dynamic variables' selected values.
|
||||
* e.g. `k8s.namespace.name IN ['zeus', 'gene'] AND doc_op_type = 'test'`
|
||||
*/
|
||||
export function buildExistingDynamicVariableQuery(
|
||||
existingVariables: IDashboardVariables | null,
|
||||
currentVariableId: string,
|
||||
hasDynamicAttribute: boolean,
|
||||
): string {
|
||||
if (!existingVariables || !hasDynamicAttribute) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const queryParts: string[] = [];
|
||||
|
||||
for (const variable of Object.values(existingVariables)) {
|
||||
// Skip the current variable being processed
|
||||
if (!isEligibleFilterVariable(variable, currentVariableId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rawValues = Array.isArray(variable.selectedValue)
|
||||
? variable.selectedValue
|
||||
: [variable.selectedValue];
|
||||
|
||||
// Filter out empty values and convert to strings
|
||||
const validValues = rawValues
|
||||
.filter(
|
||||
(val): val is string | number | boolean =>
|
||||
val !== null && val !== undefined && val !== '',
|
||||
)
|
||||
.map((val) => val.toString());
|
||||
|
||||
if (validValues.length > 0 && variable.dynamicVariablesAttribute) {
|
||||
queryParts.push(
|
||||
buildQueryPart(variable.dynamicVariablesAttribute, validValues),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return queryParts.join(' AND ');
|
||||
}
|
||||
|
||||
function isVariableInActiveFetchState(state: string | undefined): boolean {
|
||||
return state === 'loading' || state === 'revalidating';
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes or fails a variable's fetch state machine transition.
|
||||
* No-ops if the variable is not currently in an active fetch state.
|
||||
*/
|
||||
export function settleVariableFetch(
|
||||
name: string | undefined,
|
||||
outcome: 'complete' | 'failure',
|
||||
): void {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentState = variableFetchStore.getSnapshot().states[name];
|
||||
if (!isVariableInActiveFetchState(currentState)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outcome === 'complete') {
|
||||
onVariableFetchComplete(name);
|
||||
} else {
|
||||
onVariableFetchFailure(name);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractErrorMessage(
|
||||
error: { message?: string } | null,
|
||||
): string {
|
||||
if (!error) {
|
||||
return SOMETHING_WENT_WRONG;
|
||||
}
|
||||
return (
|
||||
error.message ||
|
||||
'Please make sure configuration is valid and you have required setup and permissions'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
import { areArraysEqual, onUpdateVariableNode, VariableGraph } from './util';
|
||||
jest.mock('providers/Dashboard/store/variableFetchStore', () => ({
|
||||
variableFetchStore: {
|
||||
getSnapshot: jest.fn(),
|
||||
},
|
||||
onVariableFetchComplete: jest.fn(),
|
||||
onVariableFetchFailure: jest.fn(),
|
||||
}));
|
||||
|
||||
import {
|
||||
onVariableFetchComplete,
|
||||
onVariableFetchFailure,
|
||||
variableFetchStore,
|
||||
} from 'providers/Dashboard/store/variableFetchStore';
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import {
|
||||
areArraysEqual,
|
||||
buildExistingDynamicVariableQuery,
|
||||
extractErrorMessage,
|
||||
mergeUniqueStrings,
|
||||
onUpdateVariableNode,
|
||||
settleVariableFetch,
|
||||
VariableGraph,
|
||||
} from './util';
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Existing tests
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('areArraysEqual', () => {
|
||||
it('should return true for equal arrays with same order', () => {
|
||||
@@ -149,3 +176,348 @@ describe('onUpdateVariableNode', () => {
|
||||
expect(visited).toEqual(['namespace', 'service', 'pod']);
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// New tests for functions added in recent commits
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeDynamicVar(
|
||||
overrides: Partial<IDashboardVariable> & { id: string },
|
||||
): IDashboardVariable {
|
||||
return {
|
||||
name: overrides.id,
|
||||
description: '',
|
||||
type: 'DYNAMIC',
|
||||
sort: 'DISABLED',
|
||||
multiSelect: false,
|
||||
showALLOption: false,
|
||||
allSelected: false,
|
||||
dynamicVariablesAttribute: 'attr',
|
||||
selectedValue: 'some-value',
|
||||
...overrides,
|
||||
} as IDashboardVariable;
|
||||
}
|
||||
|
||||
describe('mergeUniqueStrings', () => {
|
||||
it('should merge two arrays and deduplicate', () => {
|
||||
expect(mergeUniqueStrings(['a', 'b'], ['b', 'c'])).toEqual(['a', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('should convert numbers and booleans to strings', () => {
|
||||
expect(mergeUniqueStrings([1, true, 'hello'], [2, false])).toEqual([
|
||||
'1',
|
||||
'true',
|
||||
'hello',
|
||||
'2',
|
||||
'false',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should deduplicate when number and its string form both appear', () => {
|
||||
expect(mergeUniqueStrings([42], ['42'])).toEqual(['42']);
|
||||
});
|
||||
|
||||
it('should handle a single array', () => {
|
||||
expect(mergeUniqueStrings(['x', 'y', 'x'])).toEqual(['x', 'y']);
|
||||
});
|
||||
|
||||
it('should handle three or more arrays', () => {
|
||||
expect(mergeUniqueStrings(['a'], ['b'], ['c'], ['a', 'c'])).toEqual([
|
||||
'a',
|
||||
'b',
|
||||
'c',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array when no arrays are provided', () => {
|
||||
expect(mergeUniqueStrings()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when all input arrays are empty', () => {
|
||||
expect(mergeUniqueStrings([], [], [])).toEqual([]);
|
||||
});
|
||||
|
||||
it('should preserve order of first occurrence', () => {
|
||||
expect(mergeUniqueStrings(['c', 'a'], ['b', 'a'])).toEqual(['c', 'a', 'b']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildExistingDynamicVariableQuery', () => {
|
||||
// --- Guard clauses ---
|
||||
it('should return empty string when existingVariables is null', () => {
|
||||
expect(buildExistingDynamicVariableQuery(null, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string when hasDynamicAttribute is false', () => {
|
||||
const variables = { v2: makeDynamicVar({ id: 'v2' }) };
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', false)).toBe('');
|
||||
});
|
||||
|
||||
// --- Eligibility filtering ---
|
||||
it('should skip the current variable (same id)', () => {
|
||||
const variables = {
|
||||
v1: makeDynamicVar({
|
||||
id: 'v1',
|
||||
dynamicVariablesAttribute: 'ns',
|
||||
selectedValue: 'prod',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip non-DYNAMIC variables', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({ id: 'v2', type: 'QUERY' as any }),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip variables without dynamicVariablesAttribute', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: undefined,
|
||||
selectedValue: 'val',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip variables with null selectedValue', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({ id: 'v2', selectedValue: null }),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip variables with empty string selectedValue', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({ id: 'v2', selectedValue: '' }),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip variables with empty array selectedValue', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({ id: 'v2', selectedValue: [] }),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should skip variables where showALLOption and allSelected are both true', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
showALLOption: true,
|
||||
allSelected: true,
|
||||
dynamicVariablesAttribute: 'ns',
|
||||
selectedValue: 'prod',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
|
||||
it('should include variable with showALLOption true but allSelected false', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
showALLOption: true,
|
||||
allSelected: false,
|
||||
dynamicVariablesAttribute: 'ns',
|
||||
selectedValue: 'prod',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"ns = 'prod'",
|
||||
);
|
||||
});
|
||||
|
||||
// --- Value formatting ---
|
||||
it('should quote string values in the query', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'k8s.namespace.name',
|
||||
selectedValue: 'zeus',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"k8s.namespace.name = 'zeus'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should leave numeric values unquoted', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'http.status_code',
|
||||
selectedValue: '200',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
'http.status_code = 200',
|
||||
);
|
||||
});
|
||||
|
||||
it('should escape single quotes in string values', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'user.name',
|
||||
selectedValue: "O'Brien",
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"user.name = 'O\\'Brien'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should build an IN clause for array selectedValue with multiple items', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'k8s.namespace.name',
|
||||
selectedValue: ['zeus', 'gene'],
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"k8s.namespace.name IN ['zeus', 'gene']",
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle mix of numeric and string values in IN clause', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'http.status_code',
|
||||
selectedValue: ['200', 'unknown'],
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"http.status_code IN [200, 'unknown']",
|
||||
);
|
||||
});
|
||||
|
||||
it('should filter out empty string values from array', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'region',
|
||||
selectedValue: ['us-east', '', 'eu-west'],
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"region IN ['us-east', 'eu-west']",
|
||||
);
|
||||
});
|
||||
|
||||
// --- Multiple siblings ---
|
||||
it('should join multiple sibling variables with AND', () => {
|
||||
const variables = {
|
||||
v2: makeDynamicVar({
|
||||
id: 'v2',
|
||||
dynamicVariablesAttribute: 'k8s.namespace.name',
|
||||
selectedValue: ['zeus', 'gene'],
|
||||
}),
|
||||
v3: makeDynamicVar({
|
||||
id: 'v3',
|
||||
dynamicVariablesAttribute: 'doc_op_type',
|
||||
selectedValue: 'test',
|
||||
}),
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe(
|
||||
"k8s.namespace.name IN ['zeus', 'gene'] AND doc_op_type = 'test'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty string when no variables are eligible', () => {
|
||||
const variables = {
|
||||
v1: makeDynamicVar({ id: 'v1' }), // same as current — skipped
|
||||
v2: makeDynamicVar({ id: 'v2', type: 'QUERY' as any }), // not DYNAMIC
|
||||
v3: makeDynamicVar({ id: 'v3', selectedValue: null }), // no value
|
||||
};
|
||||
expect(buildExistingDynamicVariableQuery(variables, 'v1', true)).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('settleVariableFetch', () => {
|
||||
const mockGetSnapshot = variableFetchStore.getSnapshot as jest.Mock;
|
||||
const mockComplete = onVariableFetchComplete as jest.Mock;
|
||||
const mockFailure = onVariableFetchFailure as jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should no-op when name is undefined', () => {
|
||||
settleVariableFetch(undefined, 'complete');
|
||||
expect(mockGetSnapshot).not.toHaveBeenCalled();
|
||||
expect(mockComplete).not.toHaveBeenCalled();
|
||||
expect(mockFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['idle', 'waiting', 'error', undefined] as const)(
|
||||
'should no-op when variable state is %s',
|
||||
(state) => {
|
||||
mockGetSnapshot.mockReturnValue({ states: { myVar: state } });
|
||||
settleVariableFetch('myVar', 'complete');
|
||||
expect(mockComplete).not.toHaveBeenCalled();
|
||||
expect(mockFailure).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it('should call onVariableFetchComplete when state is loading and outcome is complete', () => {
|
||||
mockGetSnapshot.mockReturnValue({ states: { myVar: 'loading' } });
|
||||
settleVariableFetch('myVar', 'complete');
|
||||
expect(mockComplete).toHaveBeenCalledWith('myVar');
|
||||
expect(mockFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onVariableFetchComplete when state is revalidating and outcome is complete', () => {
|
||||
mockGetSnapshot.mockReturnValue({ states: { myVar: 'revalidating' } });
|
||||
settleVariableFetch('myVar', 'complete');
|
||||
expect(mockComplete).toHaveBeenCalledWith('myVar');
|
||||
expect(mockFailure).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onVariableFetchFailure when state is loading and outcome is failure', () => {
|
||||
mockGetSnapshot.mockReturnValue({ states: { myVar: 'loading' } });
|
||||
settleVariableFetch('myVar', 'failure');
|
||||
expect(mockFailure).toHaveBeenCalledWith('myVar');
|
||||
expect(mockComplete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onVariableFetchFailure when state is revalidating and outcome is failure', () => {
|
||||
mockGetSnapshot.mockReturnValue({ states: { myVar: 'revalidating' } });
|
||||
settleVariableFetch('myVar', 'failure');
|
||||
expect(mockFailure).toHaveBeenCalledWith('myVar');
|
||||
expect(mockComplete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractErrorMessage', () => {
|
||||
const FALLBACK_MESSAGE =
|
||||
'Please make sure configuration is valid and you have required setup and permissions';
|
||||
|
||||
it('should return SOMETHING_WENT_WRONG when error is null', () => {
|
||||
expect(extractErrorMessage(null)).toBe('Something went wrong');
|
||||
});
|
||||
|
||||
it('should return the error message when it exists', () => {
|
||||
expect(extractErrorMessage({ message: 'Query timeout' })).toBe(
|
||||
'Query timeout',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return fallback when error object has no message property', () => {
|
||||
expect(extractErrorMessage({})).toBe(FALLBACK_MESSAGE);
|
||||
});
|
||||
|
||||
it('should return fallback when error.message is empty string', () => {
|
||||
expect(extractErrorMessage({ message: '' })).toBe(FALLBACK_MESSAGE);
|
||||
});
|
||||
|
||||
it('should return fallback when error.message is undefined', () => {
|
||||
expect(extractErrorMessage({ message: undefined })).toBe(FALLBACK_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
import { VariableItemProps } from '../VariableItem';
|
||||
|
||||
export interface VariableSelectStrategy {
|
||||
handleChange(params: {
|
||||
value: string | string[];
|
||||
variableData: IDashboardVariable;
|
||||
variableData: VariableItemProps['variableData'];
|
||||
onValueUpdate: VariableItemProps['onValueUpdate'];
|
||||
optionsData: (string | number | boolean)[];
|
||||
allAvailableOptionStrings: string[];
|
||||
onValueUpdate: (
|
||||
name: string,
|
||||
id: string,
|
||||
value: IDashboardVariable['selectedValue'],
|
||||
allSelected: boolean,
|
||||
haveCustomValuesSelected?: boolean,
|
||||
) => void;
|
||||
}): void;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,19 @@ import { IDashboardVariable } from 'types/api/dashboard/getAll';
|
||||
|
||||
import DynamicVariableInput from '../DashboardVariablesSelection/DynamicVariableInput';
|
||||
|
||||
// Mock useVariableFetchState to return "fetching" state so useQuery is enabled
|
||||
jest.mock('hooks/dashboard/useVariableFetchState', () => ({
|
||||
useVariableFetchState: (): Record<string, unknown> => ({
|
||||
variableFetchCycleId: 0,
|
||||
variableFetchState: 'loading',
|
||||
isVariableSettled: false,
|
||||
isVariableFetching: true,
|
||||
hasVariableFetchedOnce: false,
|
||||
isVariableWaitingForDependencies: false,
|
||||
variableDependencyWaitMessage: '',
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock the getFieldValues API
|
||||
jest.mock('api/dynamicVariables/getFieldValues', () => ({
|
||||
getFieldValues: jest.fn(),
|
||||
@@ -95,7 +108,7 @@ describe('Dynamic Variable Default Behavior', () => {
|
||||
}
|
||||
}
|
||||
if (queryFn) {
|
||||
queryFn();
|
||||
queryFn({ signal: undefined });
|
||||
}
|
||||
}
|
||||
}, [enabled, variableName, dynamicVarsKey]); // Only depend on enabled/keys
|
||||
@@ -234,6 +247,7 @@ describe('Dynamic Variable Default Behavior', () => {
|
||||
'2023-01-01T00:00:00Z',
|
||||
'2023-01-02T00:00:00Z',
|
||||
'',
|
||||
undefined, // signal
|
||||
);
|
||||
});
|
||||
|
||||
@@ -487,6 +501,7 @@ describe('Dynamic Variable Default Behavior', () => {
|
||||
'2023-01-01T00:00:00Z',
|
||||
'2023-01-02T00:00:00Z',
|
||||
'',
|
||||
undefined, // signal
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -49,15 +49,11 @@ const mockDashboard = {
|
||||
// Mock the dashboard provider with stable functions to prevent infinite loops
|
||||
const mockSetSelectedDashboard = jest.fn();
|
||||
const mockUpdateLocalStorageDashboardVariables = jest.fn();
|
||||
const mockSetVariablesToGetUpdated = jest.fn();
|
||||
|
||||
jest.mock('providers/Dashboard/Dashboard', () => ({
|
||||
useDashboard: (): any => ({
|
||||
selectedDashboard: mockDashboard,
|
||||
setSelectedDashboard: mockSetSelectedDashboard,
|
||||
updateLocalStorageDashboardVariables: mockUpdateLocalStorageDashboardVariables,
|
||||
variablesToGetUpdated: ['env'], // Stable initial value
|
||||
setVariablesToGetUpdated: mockSetVariablesToGetUpdated,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { AlignedData } from 'uplot';
|
||||
|
||||
import { getInitialStackedBands, stack } from '../stackUtils';
|
||||
|
||||
describe('stackUtils', () => {
|
||||
describe('stack', () => {
|
||||
const neverOmit = (): boolean => false;
|
||||
|
||||
it('preserves time axis as first row', () => {
|
||||
const data: AlignedData = [
|
||||
[100, 200, 300],
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
];
|
||||
const { data: result } = stack(data, neverOmit);
|
||||
expect(result[0]).toEqual([100, 200, 300]);
|
||||
});
|
||||
|
||||
it('stacks value series cumulatively (last = raw, first = total)', () => {
|
||||
// Time, then 3 value series. Stack order: last series stays raw, then we add upward.
|
||||
const data: AlignedData = [
|
||||
[0, 1, 2],
|
||||
[1, 2, 3], // series 1
|
||||
[4, 5, 6], // series 2
|
||||
[7, 8, 9], // series 3
|
||||
];
|
||||
const { data: result } = stack(data, neverOmit);
|
||||
// result[1] = s1+s2+s3, result[2] = s2+s3, result[3] = s3
|
||||
expect(result[1]).toEqual([12, 15, 18]); // 1+4+7, 2+5+8, 3+6+9
|
||||
expect(result[2]).toEqual([11, 13, 15]); // 4+7, 5+8, 6+9
|
||||
expect(result[3]).toEqual([7, 8, 9]);
|
||||
});
|
||||
|
||||
it('treats null values as 0 when stacking', () => {
|
||||
const data: AlignedData = [
|
||||
[0, 1],
|
||||
[1, null],
|
||||
[null, 10],
|
||||
];
|
||||
const { data: result } = stack(data, neverOmit);
|
||||
expect(result[1]).toEqual([1, 10]); // total
|
||||
expect(result[2]).toEqual([0, 10]); // last series with null→0
|
||||
});
|
||||
|
||||
it('copies omitted series as-is without accumulating', () => {
|
||||
// Omit series 2 (index 2); series 1 and 3 are stacked.
|
||||
const data: AlignedData = [
|
||||
[0, 1],
|
||||
[10, 20], // series 1
|
||||
[100, 200], // series 2 - omitted
|
||||
[1, 2], // series 3
|
||||
];
|
||||
const omitSeries2 = (i: number): boolean => i === 2;
|
||||
const { data: result } = stack(data, omitSeries2);
|
||||
// series 3 raw: [1, 2]; series 2 omitted: [100, 200] as-is; series 1 stacked with s3: [11, 22]
|
||||
expect(result[1]).toEqual([11, 22]); // 10+1, 20+2
|
||||
expect(result[2]).toEqual([100, 200]); // copied, not stacked
|
||||
expect(result[3]).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
it('returns bands between consecutive visible series when none omitted', () => {
|
||||
const data: AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
];
|
||||
const { bands } = stack(data, neverOmit);
|
||||
expect(bands).toEqual([{ series: [1, 2] }, { series: [2, 3] }]);
|
||||
});
|
||||
|
||||
it('returns bands only between visible series when some are omitted', () => {
|
||||
// 4 value series; omit index 2. Visible: 1, 3, 4. Bands: [1,3], [3,4]
|
||||
const data: AlignedData = [[0], [1], [2], [3], [4]];
|
||||
const omitSeries2 = (i: number): boolean => i === 2;
|
||||
const { bands } = stack(data, omitSeries2);
|
||||
expect(bands).toEqual([{ series: [1, 3] }, { series: [3, 4] }]);
|
||||
});
|
||||
|
||||
it('returns empty bands when only one value series', () => {
|
||||
const data: AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
];
|
||||
const { bands } = stack(data, neverOmit);
|
||||
expect(bands).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInitialStackedBands', () => {
|
||||
it('returns one band between each consecutive pair for seriesCount 3', () => {
|
||||
expect(getInitialStackedBands(3)).toEqual([
|
||||
{ series: [1, 2] },
|
||||
{ series: [2, 3] },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns empty array for seriesCount 0 or 1', () => {
|
||||
expect(getInitialStackedBands(0)).toEqual([]);
|
||||
expect(getInitialStackedBands(1)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns single band for seriesCount 2', () => {
|
||||
expect(getInitialStackedBands(2)).toEqual([{ series: [1, 2] }]);
|
||||
});
|
||||
|
||||
it('returns bands [1,2], [2,3], ..., [n-1, n] for seriesCount n', () => {
|
||||
const bands = getInitialStackedBands(5);
|
||||
expect(bands).toEqual([
|
||||
{ series: [1, 2] },
|
||||
{ series: [2, 3] },
|
||||
{ series: [3, 4] },
|
||||
{ series: [4, 5] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import uPlot, { AlignedData } from 'uplot';
|
||||
|
||||
/**
|
||||
* Stack data cumulatively (top-down: first series = top, last = bottom).
|
||||
* When `omit(seriesIndex)` returns true, that series is excluded from stacking.
|
||||
*/
|
||||
export function stack(
|
||||
data: AlignedData,
|
||||
omit: (seriesIndex: number) => boolean,
|
||||
): { data: AlignedData; bands: uPlot.Band[] } {
|
||||
const timeAxis = data[0];
|
||||
const pointCount = timeAxis.length;
|
||||
const valueSeriesCount = data.length - 1; // exclude time axis
|
||||
|
||||
const stackedSeries = buildStackedSeries({
|
||||
data,
|
||||
valueSeriesCount,
|
||||
pointCount,
|
||||
omit,
|
||||
});
|
||||
const bands = buildFillBands(valueSeriesCount + 1, omit); // +1 for 1-based series indices
|
||||
|
||||
return {
|
||||
data: [timeAxis, ...stackedSeries] as AlignedData,
|
||||
bands,
|
||||
};
|
||||
}
|
||||
|
||||
interface BuildStackedSeriesParams {
|
||||
data: AlignedData;
|
||||
valueSeriesCount: number;
|
||||
pointCount: number;
|
||||
omit: (seriesIndex: number) => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate from last series upward: last series = raw values, first = total.
|
||||
* Omitted series are copied as-is (no accumulation).
|
||||
*/
|
||||
function buildStackedSeries({
|
||||
data,
|
||||
valueSeriesCount,
|
||||
pointCount,
|
||||
omit,
|
||||
}: BuildStackedSeriesParams): (number | null)[][] {
|
||||
const stackedSeries: (number | null)[][] = Array(valueSeriesCount);
|
||||
const cumulativeSums = Array(pointCount).fill(0) as number[];
|
||||
|
||||
for (let seriesIndex = valueSeriesCount; seriesIndex >= 1; seriesIndex--) {
|
||||
const rawValues = data[seriesIndex] as (number | null)[];
|
||||
|
||||
if (omit(seriesIndex)) {
|
||||
stackedSeries[seriesIndex - 1] = rawValues;
|
||||
} else {
|
||||
stackedSeries[seriesIndex - 1] = rawValues.map((rawValue, pointIndex) => {
|
||||
const numericValue = rawValue == null ? 0 : Number(rawValue);
|
||||
return (cumulativeSums[pointIndex] += numericValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return stackedSeries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bands define fill between consecutive visible series for stacked appearance.
|
||||
* uPlot format: [upperSeriesIdx, lowerSeriesIdx].
|
||||
*/
|
||||
function buildFillBands(
|
||||
seriesLength: number,
|
||||
omit: (seriesIndex: number) => boolean,
|
||||
): uPlot.Band[] {
|
||||
const bands: uPlot.Band[] = [];
|
||||
|
||||
for (let seriesIndex = 1; seriesIndex < seriesLength; seriesIndex++) {
|
||||
if (omit(seriesIndex)) {
|
||||
continue;
|
||||
}
|
||||
const nextVisibleSeriesIndex = findNextVisibleSeriesIndex(
|
||||
seriesLength,
|
||||
seriesIndex,
|
||||
omit,
|
||||
);
|
||||
if (nextVisibleSeriesIndex !== -1) {
|
||||
bands.push({ series: [seriesIndex, nextVisibleSeriesIndex] });
|
||||
}
|
||||
}
|
||||
|
||||
return bands;
|
||||
}
|
||||
|
||||
function findNextVisibleSeriesIndex(
|
||||
seriesLength: number,
|
||||
afterIndex: number,
|
||||
omit: (seriesIndex: number) => boolean,
|
||||
): number {
|
||||
for (let i = afterIndex + 1; i < seriesLength; i++) {
|
||||
if (!omit(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 seriesIndex = 1; seriesIndex < seriesCount; seriesIndex++) {
|
||||
bands.push({ series: [seriesIndex, seriesIndex + 1] });
|
||||
}
|
||||
return bands;
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import type { UseBarChartStackingParams } from '../useBarChartStacking';
|
||||
import { useBarChartStacking } from '../useBarChartStacking';
|
||||
|
||||
type MockConfig = { addHook: jest.Mock };
|
||||
|
||||
function asConfig(c: MockConfig): UseBarChartStackingParams['config'] {
|
||||
return (c as unknown) as UseBarChartStackingParams['config'];
|
||||
}
|
||||
|
||||
function createMockConfig(): {
|
||||
config: MockConfig;
|
||||
invokeSetData: (plot: uPlot) => void;
|
||||
invokeSetSeries: (
|
||||
plot: uPlot,
|
||||
seriesIndex: number | null,
|
||||
opts: Partial<uPlot.Series> & { focus?: boolean },
|
||||
) => void;
|
||||
removeSetData: jest.Mock;
|
||||
removeSetSeries: jest.Mock;
|
||||
} {
|
||||
let setDataHandler: ((plot: uPlot) => void) | null = null;
|
||||
let setSeriesHandler:
|
||||
| ((plot: uPlot, seriesIndex: number | null, opts: uPlot.Series) => void)
|
||||
| null = null;
|
||||
|
||||
const removeSetData = jest.fn();
|
||||
const removeSetSeries = jest.fn();
|
||||
|
||||
const addHook = jest.fn(
|
||||
(
|
||||
hookName: string,
|
||||
handler: (plot: uPlot, ...args: unknown[]) => void,
|
||||
): (() => void) => {
|
||||
if (hookName === 'setData') {
|
||||
setDataHandler = handler as (plot: uPlot) => void;
|
||||
return removeSetData;
|
||||
}
|
||||
if (hookName === 'setSeries') {
|
||||
setSeriesHandler = handler as (
|
||||
plot: uPlot,
|
||||
seriesIndex: number | null,
|
||||
opts: uPlot.Series,
|
||||
) => void;
|
||||
return removeSetSeries;
|
||||
}
|
||||
return jest.fn();
|
||||
},
|
||||
);
|
||||
|
||||
const config: MockConfig = { addHook };
|
||||
|
||||
const invokeSetData = (plot: uPlot): void => {
|
||||
setDataHandler?.(plot);
|
||||
};
|
||||
|
||||
const invokeSetSeries = (
|
||||
plot: uPlot,
|
||||
seriesIndex: number | null,
|
||||
opts: Partial<uPlot.Series> & { focus?: boolean },
|
||||
): void => {
|
||||
setSeriesHandler?.(plot, seriesIndex, opts as uPlot.Series);
|
||||
};
|
||||
|
||||
return {
|
||||
config,
|
||||
invokeSetData,
|
||||
invokeSetSeries,
|
||||
removeSetData,
|
||||
removeSetSeries,
|
||||
};
|
||||
}
|
||||
|
||||
function createMockPlot(overrides: Partial<uPlot> = {}): uPlot {
|
||||
return ({
|
||||
data: [
|
||||
[0, 1, 2],
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
],
|
||||
series: [{ show: true }, { show: true }, { show: true }],
|
||||
delBand: jest.fn(),
|
||||
addBand: jest.fn(),
|
||||
setData: jest.fn(),
|
||||
...overrides,
|
||||
} as unknown) as uPlot;
|
||||
}
|
||||
|
||||
describe('useBarChartStacking', () => {
|
||||
it('returns data as-is when isStackedBarChart is false', () => {
|
||||
const data: uPlot.AlignedData = [
|
||||
[100, 200],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
];
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: false,
|
||||
config: null,
|
||||
}),
|
||||
);
|
||||
expect(result.current).toBe(data);
|
||||
});
|
||||
|
||||
it('returns data as-is when config is null and isStackedBarChart is true', () => {
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[4, 5],
|
||||
];
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: null,
|
||||
}),
|
||||
);
|
||||
// Still returns stacked data (computed in useMemo); no hooks registered
|
||||
expect(result.current[0]).toEqual([0, 1]);
|
||||
expect(result.current[1]).toEqual([5, 7]); // stacked
|
||||
expect(result.current[2]).toEqual([4, 5]);
|
||||
});
|
||||
|
||||
it('returns stacked data when isStackedBarChart is true and multiple value series', () => {
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1, 2],
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
];
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: null,
|
||||
}),
|
||||
);
|
||||
expect(result.current[0]).toEqual([0, 1, 2]);
|
||||
expect(result.current[1]).toEqual([12, 15, 18]); // s1+s2+s3
|
||||
expect(result.current[2]).toEqual([11, 13, 15]); // s2+s3
|
||||
expect(result.current[3]).toEqual([7, 8, 9]);
|
||||
});
|
||||
|
||||
it('returns data as-is when only one value series (no stacking needed)', () => {
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
];
|
||||
const { result } = renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: null,
|
||||
}),
|
||||
);
|
||||
expect(result.current).toEqual(data);
|
||||
});
|
||||
|
||||
it('registers setData and setSeries hooks when isStackedBarChart and config provided', () => {
|
||||
const { config } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
];
|
||||
|
||||
renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(config.addHook).toHaveBeenCalledWith('setData', expect.any(Function));
|
||||
expect(config.addHook).toHaveBeenCalledWith(
|
||||
'setSeries',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not register hooks when isStackedBarChart is false', () => {
|
||||
const { config } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
];
|
||||
|
||||
renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: false,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(config.addHook).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls cleanup when unmounted', () => {
|
||||
const { config, removeSetData, removeSetSeries } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
];
|
||||
|
||||
const { unmount } = renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
unmount();
|
||||
|
||||
expect(removeSetData).toHaveBeenCalled();
|
||||
expect(removeSetSeries).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('re-stacks and updates plot when setData hook is invoked', () => {
|
||||
const { config, invokeSetData } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1, 2],
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
];
|
||||
const plot = createMockPlot({
|
||||
data: [
|
||||
[0, 1, 2],
|
||||
[5, 7, 9],
|
||||
[4, 5, 6],
|
||||
],
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
invokeSetData(plot);
|
||||
|
||||
expect(plot.delBand).toHaveBeenCalledWith(null);
|
||||
expect(plot.addBand).toHaveBeenCalled();
|
||||
expect(plot.setData).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
[0, 1, 2],
|
||||
expect.any(Array), // stacked row 1
|
||||
expect.any(Array), // stacked row 2
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('re-stacks when setSeries hook is invoked (e.g. legend toggle)', () => {
|
||||
const { config, invokeSetSeries } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[10, 20],
|
||||
[5, 10],
|
||||
];
|
||||
// Plot data must match unstacked length so canApplyStacking passes
|
||||
const plot = createMockPlot({
|
||||
data: [
|
||||
[0, 1],
|
||||
[15, 30],
|
||||
[5, 10],
|
||||
],
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
invokeSetSeries(plot, 1, { show: false });
|
||||
|
||||
expect(plot.setData).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not re-stack when setSeries is called with focus option', () => {
|
||||
const { config, invokeSetSeries } = createMockConfig();
|
||||
const data: uPlot.AlignedData = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
];
|
||||
const plot = createMockPlot();
|
||||
|
||||
renderHook(() =>
|
||||
useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart: true,
|
||||
config: asConfig(config),
|
||||
}),
|
||||
);
|
||||
|
||||
(plot.setData as jest.Mock).mockClear();
|
||||
invokeSetSeries(plot, 1, { focus: true } as uPlot.Series);
|
||||
|
||||
expect(plot.setData).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
VisibilityMode,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { get } from 'lodash-es';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@@ -77,6 +78,12 @@ export function prepareBarPanelConfig({
|
||||
builder.setBands(getInitialStackedBands(seriesCount));
|
||||
}
|
||||
|
||||
const stepIntervals: Record<string, number> = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
);
|
||||
|
||||
const seriesList: QueryData[] = apiResponse?.data?.result || [];
|
||||
seriesList.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
@@ -89,6 +96,8 @@ export function prepareBarPanelConfig({
|
||||
? getLegend(series, currentQuery, baseLabelName)
|
||||
: baseLabelName;
|
||||
|
||||
const currentStepInterval = get(stepIntervals, series.queryName, undefined);
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Bar,
|
||||
@@ -101,6 +110,7 @@ export function prepareBarPanelConfig({
|
||||
showPoints: VisibilityMode.Never,
|
||||
pointSize: 5,
|
||||
isDarkMode,
|
||||
stepInterval: currentStepInterval,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,11 +14,6 @@ export interface GraphVisibilityState {
|
||||
dataIndex: SeriesVisibilityItem[];
|
||||
}
|
||||
|
||||
export interface SeriesVisibilityState {
|
||||
labels: string[];
|
||||
visibility: boolean[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Context in which a panel is rendered. Used to vary behavior (e.g. persistence,
|
||||
* interactions) per context.
|
||||
|
||||
@@ -62,10 +62,10 @@ describe('legendVisibilityUtils', () => {
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result).toEqual({
|
||||
labels: ['CPU', 'Memory'],
|
||||
visibility: [true, false],
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns visibility by index including duplicate labels', () => {
|
||||
@@ -85,10 +85,11 @@ describe('legendVisibilityUtils', () => {
|
||||
const result = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result).toEqual({
|
||||
labels: ['CPU', 'CPU', 'Memory'],
|
||||
visibility: [true, false, false],
|
||||
});
|
||||
expect(result).toEqual([
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'CPU', show: false },
|
||||
{ label: 'Memory', show: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns null on malformed JSON in localStorage', () => {
|
||||
@@ -127,10 +128,10 @@ describe('legendVisibilityUtils', () => {
|
||||
const stored = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored).toEqual({
|
||||
labels: ['CPU', 'Memory'],
|
||||
visibility: [true, false],
|
||||
});
|
||||
expect(stored).toEqual([
|
||||
{ label: 'CPU', show: true },
|
||||
{ label: 'Memory', show: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('adds a new widget entry when other widgets already exist', () => {
|
||||
@@ -149,7 +150,7 @@ describe('legendVisibilityUtils', () => {
|
||||
const stored = getStoredSeriesVisibility('widget-new');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored).toEqual({ labels: ['CPU'], visibility: [false] });
|
||||
expect(stored).toEqual([{ label: 'CPU', show: false }]);
|
||||
});
|
||||
|
||||
it('updates existing widget visibility when entry already exists', () => {
|
||||
@@ -175,10 +176,10 @@ describe('legendVisibilityUtils', () => {
|
||||
const stored = getStoredSeriesVisibility('widget-1');
|
||||
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored).toEqual({
|
||||
labels: ['CPU', 'Memory'],
|
||||
visibility: [false, true],
|
||||
});
|
||||
expect(stored).toEqual([
|
||||
{ label: 'CPU', show: false },
|
||||
{ label: 'Memory', show: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('silently handles malformed existing JSON without throwing', () => {
|
||||
@@ -201,10 +202,10 @@ describe('legendVisibilityUtils', () => {
|
||||
|
||||
const stored = getStoredSeriesVisibility('widget-1');
|
||||
expect(stored).not.toBeNull();
|
||||
expect(stored).toEqual({
|
||||
labels: ['x-axis', 'CPU'],
|
||||
visibility: [true, false],
|
||||
});
|
||||
expect(stored).toEqual([
|
||||
{ label: 'x-axis', show: true },
|
||||
{ label: 'CPU', show: false },
|
||||
]);
|
||||
const expected = [
|
||||
{
|
||||
name: 'widget-1',
|
||||
@@ -231,14 +232,12 @@ describe('legendVisibilityUtils', () => {
|
||||
{ label: 'B', show: true },
|
||||
]);
|
||||
|
||||
expect(getStoredSeriesVisibility('widget-a')).toEqual({
|
||||
labels: ['A'],
|
||||
visibility: [true],
|
||||
});
|
||||
expect(getStoredSeriesVisibility('widget-b')).toEqual({
|
||||
labels: ['B'],
|
||||
visibility: [true],
|
||||
});
|
||||
expect(getStoredSeriesVisibility('widget-a')).toEqual([
|
||||
{ label: 'A', show: true },
|
||||
]);
|
||||
expect(getStoredSeriesVisibility('widget-b')).toEqual([
|
||||
{ label: 'B', show: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('calls setItem with storage key and stringified visibility states', () => {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
import {
|
||||
GraphVisibilityState,
|
||||
SeriesVisibilityItem,
|
||||
SeriesVisibilityState,
|
||||
} from '../types';
|
||||
import { GraphVisibilityState, SeriesVisibilityItem } from '../types';
|
||||
|
||||
/**
|
||||
* Retrieves the stored series visibility for a specific widget from localStorage by index.
|
||||
@@ -14,7 +10,7 @@ import {
|
||||
*/
|
||||
export function getStoredSeriesVisibility(
|
||||
widgetId: string,
|
||||
): SeriesVisibilityState | null {
|
||||
): SeriesVisibilityItem[] | null {
|
||||
try {
|
||||
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
|
||||
@@ -29,10 +25,7 @@ export function getStoredSeriesVisibility(
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
labels: widgetState.dataIndex.map((item) => item.label),
|
||||
visibility: widgetState.dataIndex.map((item) => item.show),
|
||||
};
|
||||
return widgetState.dataIndex;
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
// If the stored data is malformed, remove it
|
||||
|
||||
@@ -6,10 +6,12 @@ import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util';
|
||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import { useIsPanelWaitingOnVariable } from 'hooks/dashboard/useVariableFetchState';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { getDashboardVariables } from 'lib/dashboardVariables/getDashboardVariables';
|
||||
import { getVariableReferencesInQuery } from 'lib/dashboardVariables/variableReference';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
@@ -53,7 +55,6 @@ function GridCardGraph({
|
||||
customOnRowClick,
|
||||
customTimeRangeWindowForCoRelation,
|
||||
enableDrillDown,
|
||||
widgetsByDynamicVariableId,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const dispatch = useDispatch();
|
||||
const [errorMessage, setErrorMessage] = useState<string>();
|
||||
@@ -64,8 +65,8 @@ function GridCardGraph({
|
||||
toScrollWidgetId,
|
||||
setToScrollWidgetId,
|
||||
setDashboardQueryRangeCalled,
|
||||
variablesToGetUpdated,
|
||||
} = useDashboard();
|
||||
|
||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
@@ -117,10 +118,25 @@ function GridCardGraph({
|
||||
|
||||
const updatedQuery = widget?.query;
|
||||
|
||||
const referencedVariableNames = useMemo(() => {
|
||||
if (!variables || !updatedQuery) {
|
||||
return [];
|
||||
}
|
||||
const allNames = Object.values(variables)
|
||||
.map((v) => v.name)
|
||||
.filter((name): name is string => !!name);
|
||||
return getVariableReferencesInQuery(updatedQuery, allNames);
|
||||
}, [updatedQuery, variables]);
|
||||
|
||||
const isEmptyWidget =
|
||||
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
|
||||
|
||||
const queryEnabledCondition = isVisible && !isEmptyWidget && isQueryEnabled;
|
||||
const isPanelWaitingOnAnyVariable = useIsPanelWaitingOnVariable(
|
||||
referencedVariableNames,
|
||||
);
|
||||
|
||||
const queryEnabledCondition =
|
||||
isVisible && !isEmptyWidget && isQueryEnabled && !isPanelWaitingOnAnyVariable;
|
||||
|
||||
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
|
||||
if (widget.panelTypes !== PANEL_TYPES.LIST) {
|
||||
@@ -177,27 +193,6 @@ function GridCardGraph({
|
||||
[requestData.query],
|
||||
);
|
||||
|
||||
// Bring back dependency on variable chaining for panels to refetch,
|
||||
// but only for non-dynamic variables. We derive a stable token from
|
||||
// the head of the variablesToGetUpdated queue when it's non-dynamic.
|
||||
const nonDynamicVariableChainToken = useMemo(() => {
|
||||
if (!variablesToGetUpdated || variablesToGetUpdated.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!variables) {
|
||||
return undefined;
|
||||
}
|
||||
const headName = variablesToGetUpdated[0];
|
||||
const variableObj = Object.values(variables).find(
|
||||
(variable) => variable?.name === headName,
|
||||
);
|
||||
if (variableObj && variableObj.type !== 'DYNAMIC') {
|
||||
return headName;
|
||||
}
|
||||
return undefined;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [variablesToGetUpdated, variables]);
|
||||
|
||||
const queryResponse = useGetQueryRange(
|
||||
{
|
||||
...requestData,
|
||||
@@ -224,11 +219,7 @@ function GridCardGraph({
|
||||
requestData,
|
||||
variables
|
||||
? Object.entries(variables).reduce((acc, [id, variable]) => {
|
||||
if (
|
||||
variable.type !== 'DYNAMIC' ||
|
||||
(widgetsByDynamicVariableId?.[variable.id] &&
|
||||
widgetsByDynamicVariableId?.[variable.id].includes(widget.id))
|
||||
) {
|
||||
if (variable.name && referencedVariableNames.includes(variable.name)) {
|
||||
return { ...acc, [id]: variable.selectedValue };
|
||||
}
|
||||
return acc;
|
||||
@@ -237,9 +228,6 @@ function GridCardGraph({
|
||||
...(customTimeRange && customTimeRange.startTime && customTimeRange.endTime
|
||||
? [customTimeRange.startTime, customTimeRange.endTime]
|
||||
: []),
|
||||
// Include non-dynamic variable chaining token to drive refetches
|
||||
// only when a non-dynamic variable is at the head of the queue
|
||||
...(nonDynamicVariableChainToken ? [nonDynamicVariableChainToken] : []),
|
||||
],
|
||||
retry(failureCount, error): boolean {
|
||||
if (
|
||||
@@ -252,7 +240,7 @@ function GridCardGraph({
|
||||
return failureCount < 2;
|
||||
},
|
||||
keepPreviousData: true,
|
||||
enabled: queryEnabledCondition && !nonDynamicVariableChainToken,
|
||||
enabled: queryEnabledCondition,
|
||||
refetchOnMount: false,
|
||||
onError: (error) => {
|
||||
const errorMessage =
|
||||
@@ -319,7 +307,7 @@ function GridCardGraph({
|
||||
threshold={threshold}
|
||||
headerMenuList={menuList}
|
||||
isFetchingResponse={
|
||||
queryResponse.isFetching || variablesToGetUpdated.length > 0
|
||||
queryResponse.isFetching || isPanelWaitingOnAnyVariable
|
||||
}
|
||||
setRequestData={setRequestData}
|
||||
onClickHandler={onClickHandler}
|
||||
|
||||
@@ -72,7 +72,6 @@ export interface GridCardGraphProps {
|
||||
customOnRowClick?: (record: RowData) => void;
|
||||
customTimeRangeWindowForCoRelation?: string | undefined;
|
||||
enableDrillDown?: boolean;
|
||||
widgetsByDynamicVariableId?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export interface GetGraphVisibilityStateOnLegendClickProps {
|
||||
|
||||
@@ -16,7 +16,6 @@ import { themeColors } from 'constants/theme';
|
||||
import { DEFAULT_ROW_NAME } from 'container/DashboardContainer/DashboardDescription/utils';
|
||||
import { useDashboardVariables } from 'hooks/dashboard/useDashboardVariables';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useWidgetsByDynamicVariableId } from 'hooks/dashboard/useWidgetsByDynamicVariableId';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
@@ -102,8 +101,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
Record<string, { widgets: Layout[]; collapsed: boolean }>
|
||||
>({});
|
||||
|
||||
const widgetsByDynamicVariableId = useWidgetsByDynamicVariableId();
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPanelMap(panelMap);
|
||||
}, [panelMap]);
|
||||
@@ -617,7 +614,6 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
|
||||
onDragSelect={onDragSelect}
|
||||
dataAvailable={checkIfDataExists}
|
||||
enableDrillDown={enableDrillDown}
|
||||
widgetsByDynamicVariableId={widgetsByDynamicVariableId}
|
||||
/>
|
||||
</Card>
|
||||
</CardContainer>
|
||||
|
||||
@@ -30,6 +30,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
@@ -210,13 +211,19 @@ function ServiceMetrics({
|
||||
|
||||
const topLevelOperations = useMemo(() => Object.entries(data || {}), [data]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryRangeRequestData = useMemo(
|
||||
() =>
|
||||
getQueryRangeRequestData({
|
||||
topLevelOperations,
|
||||
globalSelectedInterval,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
[globalSelectedInterval, topLevelOperations, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const dataQueries = useGetQueriesRange(
|
||||
|
||||
@@ -23,6 +23,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import HostsListControls from './HostsListControls';
|
||||
import HostsListTable from './HostsListTable';
|
||||
import { getHostListsQuery, GetHostsQuickFiltersConfig } from './utils';
|
||||
@@ -144,6 +146,11 @@ function HostsList(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
const isNewFilterAdded = value?.items?.length !== filters?.items?.length;
|
||||
@@ -214,7 +221,7 @@ function HostsList(): JSX.Element {
|
||||
</div>
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetHostsQuickFiltersConfig()}
|
||||
config={GetHostsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
|
||||
@@ -71,12 +71,20 @@ describe('InfraMonitoringHosts utils', () => {
|
||||
});
|
||||
|
||||
describe('GetHostsQuickFiltersConfig', () => {
|
||||
it('should return correct config with dot-notation keys', () => {
|
||||
const result = GetHostsQuickFiltersConfig();
|
||||
it('should return correct config when dotMetricsEnabled is true', () => {
|
||||
const result = GetHostsQuickFiltersConfig(true);
|
||||
|
||||
expect(result[0].attributeKey.key).toBe('host.name');
|
||||
expect(result[1].attributeKey.key).toBe('os.type');
|
||||
expect(result[0].aggregateAttribute).toBe('system.cpu.load_average.15m');
|
||||
});
|
||||
|
||||
it('should return correct config when dotMetricsEnabled is false', () => {
|
||||
const result = GetHostsQuickFiltersConfig(false);
|
||||
|
||||
expect(result[0].attributeKey.key).toBe('host_name');
|
||||
expect(result[1].attributeKey.key).toBe('os_type');
|
||||
expect(result[0].aggregateAttribute).toBe('system_cpu_load_average_15m');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -211,18 +211,32 @@ export const HostsQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function GetHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetHostsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
// These keys don’t change with dotMetricsEnabled
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const osTypeKey = dotMetricsEnabled ? 'os.type' : 'os_type';
|
||||
// This metric stays the same regardless of notation
|
||||
const metricName = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
: 'system_cpu_load_average_15m';
|
||||
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Host Name',
|
||||
attributeKey: {
|
||||
key: 'host.name',
|
||||
key: hostNameKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system.cpu.load_average.15m',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -230,12 +244,12 @@ export function GetHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'OS Type',
|
||||
attributeKey: {
|
||||
key: 'os.type',
|
||||
key: osTypeKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'system.cpu.load_average.15m',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -243,7 +257,7 @@ export function GetHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -46,34 +46,95 @@ export const getClusterMetricsQueryPayload = (
|
||||
cluster: K8sClustersData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sNodeAllocatableCpuKey = 'k8s.node.allocatable_cpu';
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sNodeAllocatableMemoryKey = 'k8s.node.allocatable_memory';
|
||||
const k8sNodeConditionReadyKey = 'k8s.node.condition_ready';
|
||||
const k8sDeploymentAvailableKey = 'k8s.deployment.available';
|
||||
const k8sDeploymentDesiredKey = 'k8s.deployment.desired';
|
||||
const k8sStatefulsetCurrentPodsKey = 'k8s.statefulset.current_pods';
|
||||
const k8sStatefulsetDesiredPodsKey = 'k8s.statefulset.desired_pods';
|
||||
const k8sStatefulsetReadyPodsKey = 'k8s.statefulset.ready_pods';
|
||||
const k8sStatefulsetUpdatedPodsKey = 'k8s.statefulset.updated_pods';
|
||||
const k8sDaemonsetCurrentScheduledNodesKey =
|
||||
'k8s.daemonset.current_scheduled_nodes';
|
||||
const k8sDaemonsetDesiredScheduledNodesKey =
|
||||
'k8s.daemonset.desired_scheduled_nodes';
|
||||
const k8sDaemonsetReadyNodesKey = 'k8s.daemonset.ready_nodes';
|
||||
const k8sJobActivePodsKey = 'k8s.job.active_pods';
|
||||
const k8sJobSuccessfulPodsKey = 'k8s.job.successful_pods';
|
||||
const k8sJobFailedPodsKey = 'k8s.job.failed_pods';
|
||||
const k8sJobDesiredSuccessfulPodsKey = 'k8s.job.desired_successful_pods';
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
const k8sDeploymentNameKey = 'k8s.deployment.name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sStatefulsetNameKey = 'k8s.statefulset.name';
|
||||
const k8sDaemonsetNameKey = 'k8s.daemonset.name';
|
||||
const k8sJobNameKey = 'k8s.job.name';
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sPodCpuUtilizationKey = getKey(
|
||||
'k8s.pod.cpu.usage',
|
||||
'k8s_pod_cpu_usage',
|
||||
);
|
||||
const k8sNodeAllocatableCpuKey = getKey(
|
||||
'k8s.node.allocatable_cpu',
|
||||
'k8s_node_allocatable_cpu',
|
||||
);
|
||||
const k8sPodMemoryUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
const k8sNodeAllocatableMemoryKey = getKey(
|
||||
'k8s.node.allocatable_memory',
|
||||
'k8s_node_allocatable_memory',
|
||||
);
|
||||
const k8sNodeConditionReadyKey = getKey(
|
||||
'k8s.node.condition_ready',
|
||||
'k8s_node_condition_ready',
|
||||
);
|
||||
const k8sDeploymentAvailableKey = getKey(
|
||||
'k8s.deployment.available',
|
||||
'k8s_deployment_available',
|
||||
);
|
||||
const k8sDeploymentDesiredKey = getKey(
|
||||
'k8s.deployment.desired',
|
||||
'k8s_deployment_desired',
|
||||
);
|
||||
const k8sStatefulsetCurrentPodsKey = getKey(
|
||||
'k8s.statefulset.current_pods',
|
||||
'k8s_statefulset_current_pods',
|
||||
);
|
||||
const k8sStatefulsetDesiredPodsKey = getKey(
|
||||
'k8s.statefulset.desired_pods',
|
||||
'k8s_statefulset_desired_pods',
|
||||
);
|
||||
const k8sStatefulsetReadyPodsKey = getKey(
|
||||
'k8s.statefulset.ready_pods',
|
||||
'k8s_statefulset_ready_pods',
|
||||
);
|
||||
const k8sStatefulsetUpdatedPodsKey = getKey(
|
||||
'k8s.statefulset.updated_pods',
|
||||
'k8s_statefulset_updated_pods',
|
||||
);
|
||||
const k8sDaemonsetCurrentScheduledNodesKey = getKey(
|
||||
'k8s.daemonset.current_scheduled_nodes',
|
||||
'k8s_daemonset_current_scheduled_nodes',
|
||||
);
|
||||
const k8sDaemonsetDesiredScheduledNodesKey = getKey(
|
||||
'k8s.daemonset.desired_scheduled_nodes',
|
||||
'k8s_daemonset_desired_scheduled_nodes',
|
||||
);
|
||||
const k8sDaemonsetReadyNodesKey = getKey(
|
||||
'k8s.daemonset.ready_nodes',
|
||||
'k8s_daemonset_ready_nodes',
|
||||
);
|
||||
const k8sJobActivePodsKey = getKey(
|
||||
'k8s.job.active_pods',
|
||||
'k8s_job_active_pods',
|
||||
);
|
||||
const k8sJobSuccessfulPodsKey = getKey(
|
||||
'k8s.job.successful_pods',
|
||||
'k8s_job_successful_pods',
|
||||
);
|
||||
const k8sJobFailedPodsKey = getKey(
|
||||
'k8s.job.failed_pods',
|
||||
'k8s_job_failed_pods',
|
||||
);
|
||||
const k8sJobDesiredSuccessfulPodsKey = getKey(
|
||||
'k8s.job.desired_successful_pods',
|
||||
'k8s_job_desired_successful_pods',
|
||||
);
|
||||
const k8sClusterNameKey = getKey('k8s.cluster.name', 'k8s_cluster_name');
|
||||
const k8sNodeNameKey = getKey('k8s.node.name', 'k8s_node_name');
|
||||
const k8sDeploymentNameKey = getKey(
|
||||
'k8s.deployment.name',
|
||||
'k8s_deployment_name',
|
||||
);
|
||||
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
|
||||
const k8sStatefulsetNameKey = getKey(
|
||||
'k8s.statefulset.name',
|
||||
'k8s_statefulset_name',
|
||||
);
|
||||
const k8sDaemonsetNameKey = getKey('k8s.daemonset.name', 'k8s_daemonset_name');
|
||||
const k8sJobNameKey = getKey('k8s.job.name', 'k8s_job_name');
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -25,6 +25,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -135,6 +137,11 @@ function K8sClustersList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sClustersRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -224,6 +231,8 @@ function K8sClustersList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -232,7 +241,10 @@ function K8sClustersList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.CLUSTERS),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.CLUSTERS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -313,6 +325,8 @@ function K8sClustersList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const clustersData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -136,7 +136,7 @@ export const getK8sClustersListColumns = (
|
||||
return columnsConfig as ColumnType<K8sClustersRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sClustersData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sClustersData['meta']> = {
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
'k8s.cluster.uid': 'k8s_cluster_uid',
|
||||
};
|
||||
@@ -151,8 +151,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof cluster.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof cluster.meta;
|
||||
const value = cluster.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -30,28 +30,49 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
daemonSet: K8sDaemonSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemoryRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sContainerMemoryLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
|
||||
const k8sDaemonSetNameKey = 'k8s.daemonset.name';
|
||||
const k8sDaemonSetNameKey = dotMetricsEnabled
|
||||
? 'k8s.daemonset.name'
|
||||
: 'k8s_daemonset_name';
|
||||
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -137,6 +139,11 @@ function K8sDaemonSetsList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sDaemonSetsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -226,6 +233,8 @@ function K8sDaemonSetsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -234,7 +243,10 @@ function K8sDaemonSetsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.DAEMONSETS),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DAEMONSETS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -308,6 +320,8 @@ function K8sDaemonSetsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const daemonSetsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -236,7 +236,7 @@ export const getK8sDaemonSetsListColumns = (
|
||||
return columnsConfig as ColumnType<K8sDaemonSetsRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sDaemonSetsData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sDaemonSetsData['meta']> = {
|
||||
'k8s.daemonset.name': 'k8s_daemonset_name',
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
@@ -252,8 +252,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof daemonSet.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof daemonSet.meta;
|
||||
const value = daemonSet.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -30,26 +30,45 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
deployment: K8sDeploymentsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemoryRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sContainerMemoryLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
|
||||
const k8sDeploymentNameKey = 'k8s.deployment.name';
|
||||
const k8sDeploymentNameKey = dotMetricsEnabled
|
||||
? 'k8s.deployment.name'
|
||||
: 'k8s_deployment_name';
|
||||
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -138,6 +140,11 @@ function K8sDeploymentsList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sDeploymentsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -227,6 +234,8 @@ function K8sDeploymentsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -237,6 +246,7 @@ function K8sDeploymentsList({
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DEPLOYMENTS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
@@ -311,6 +321,8 @@ function K8sDeploymentsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const deploymentsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -226,7 +226,7 @@ export const getK8sDeploymentsListColumns = (
|
||||
return columnsConfig as ColumnType<K8sDeploymentsRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sDeploymentsData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sDeploymentsData['meta']> = {
|
||||
'k8s.deployment.name': 'k8s_deployment_name',
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
@@ -242,7 +242,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
const metaKey = (dotToUnder[rawKey] ??
|
||||
rawKey) as keyof typeof deployment.meta;
|
||||
const value = deployment.meta[metaKey];
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Options } from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useMultiIntersectionObserver } from '../../../../hooks/useMultiIntersectionObserver';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
|
||||
import './entityMetrics.styles.scss';
|
||||
|
||||
@@ -52,6 +54,7 @@ interface EntityMetricsProps<T> {
|
||||
node: T,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
) => GetQueryResultsProps[];
|
||||
queryKey: string;
|
||||
category: K8sCategory;
|
||||
@@ -68,14 +71,31 @@ function EntityMetrics<T>({
|
||||
queryKey,
|
||||
category,
|
||||
}: EntityMetricsProps<T>): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const {
|
||||
visibilities,
|
||||
setElement,
|
||||
} = useMultiIntersectionObserver(entityWidgetInfo.length, { threshold: 0.1 });
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getEntityQueryPayload(entity, timeRange.startTime, timeRange.endTime),
|
||||
[getEntityQueryPayload, entity, timeRange.startTime, timeRange.endTime],
|
||||
() =>
|
||||
getEntityQueryPayload(
|
||||
entity,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
[
|
||||
getEntityQueryPayload,
|
||||
entity,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -97,7 +97,12 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
|
||||
plan_version: 'test-plan-version',
|
||||
},
|
||||
},
|
||||
featureFlags: [],
|
||||
featureFlags: [
|
||||
{
|
||||
name: 'DOT_METRICS_ENABLED',
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
const mockEntity = {
|
||||
@@ -380,6 +385,7 @@ describe('EntityMetrics', () => {
|
||||
mockEntity,
|
||||
mockTimeRange.startTime,
|
||||
mockTimeRange.endTime,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { FeatureKeys } from '../../constants/features';
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import K8sClustersList from './Clusters/K8sClustersList';
|
||||
import {
|
||||
GetClustersQuickFiltersConfig,
|
||||
@@ -74,6 +76,11 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
entityVersion: '',
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const handleFilterChange = (query: Query): void => {
|
||||
// update the current query with the new filters
|
||||
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
||||
@@ -109,7 +116,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetPodsQuickFiltersConfig()}
|
||||
config={GetPodsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -129,7 +136,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNodesQuickFiltersConfig()}
|
||||
config={GetNodesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -152,7 +159,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNamespaceQuickFiltersConfig()}
|
||||
config={GetNamespaceQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -172,7 +179,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetClustersQuickFiltersConfig()}
|
||||
config={GetClustersQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -192,7 +199,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDeploymentsQuickFiltersConfig()}
|
||||
config={GetDeploymentsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -212,7 +219,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetJobsQuickFiltersConfig()}
|
||||
config={GetJobsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -232,7 +239,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDaemonsetsQuickFiltersConfig()}
|
||||
config={GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -255,7 +262,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetStatefulsetsQuickFiltersConfig()}
|
||||
config={GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -275,7 +282,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetVolumesQuickFiltersConfig()}
|
||||
config={GetVolumesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
|
||||
@@ -30,13 +30,24 @@ export const getJobMetricsQueryPayload = (
|
||||
job: K8sJobsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sJobNameKey = 'k8s.job.name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
const k8sJobNameKey = dotMetricsEnabled ? 'k8s.job.name' : 'k8s_job_name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -132,6 +134,11 @@ function K8sJobsList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sJobsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -208,10 +215,15 @@ function K8sJobsList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sJobsList(fetchGroupedByRowDataQuery as K8sJobsListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sJobsList(
|
||||
fetchGroupedByRowDataQuery as K8sJobsListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -219,7 +231,10 @@ function K8sJobsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.JOBS),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.JOBS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -300,6 +315,8 @@ function K8sJobsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const jobsData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -263,7 +263,7 @@ export const getK8sJobsListColumns = (
|
||||
return columnsConfig as ColumnType<K8sJobsRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sJobsData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sJobsData['meta']> = {
|
||||
'k8s.job.name': 'k8s_job_name',
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
@@ -279,8 +279,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof job.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof job.meta;
|
||||
const value = job.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -25,6 +25,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -136,6 +138,11 @@ function K8sNamespacesList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sNamespacesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -225,6 +232,8 @@ function K8sNamespacesList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -233,7 +242,10 @@ function K8sNamespacesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NAMESPACES),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NAMESPACES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -307,6 +319,8 @@ function K8sNamespacesList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const namespacesData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -54,35 +54,95 @@ export const getNamespaceMetricsQueryPayload = (
|
||||
namespace: K8sNamespacesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
const k8sPodMemoryWorkingSetKey = 'k8s.pod.memory.working_set';
|
||||
const k8sPodMemoryRssKey = 'k8s.pod.memory.rss';
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sStatefulsetCurrentPodsKey = 'k8s.statefulset.current_pods';
|
||||
const k8sStatefulsetDesiredPodsKey = 'k8s.statefulset.desired_pods';
|
||||
const k8sStatefulsetUpdatedPodsKey = 'k8s.statefulset.updated_pods';
|
||||
const k8sReplicasetDesiredKey = 'k8s.replicaset.desired';
|
||||
const k8sReplicasetAvailableKey = 'k8s.replicaset.available';
|
||||
const k8sDaemonsetDesiredScheduledNamespacesKey =
|
||||
'k8s.daemonset.desired.scheduled.namespaces';
|
||||
const k8sDaemonsetCurrentScheduledNamespacesKey =
|
||||
'k8s.daemonset.current.scheduled.namespaces';
|
||||
const k8sDaemonsetReadyNamespacesKey = 'k8s.daemonset.ready.namespaces';
|
||||
const k8sDaemonsetMisscheduledNamespacesKey =
|
||||
'k8s.daemonset.misscheduled.namespaces';
|
||||
const k8sDeploymentDesiredKey = 'k8s.deployment.desired';
|
||||
const k8sDeploymentAvailableKey = 'k8s.deployment.available';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sStatefulsetNameKey = 'k8s.statefulset.name';
|
||||
const k8sReplicasetNameKey = 'k8s.replicaset.name';
|
||||
const k8sDaemonsetNameKey = 'k8s.daemonset.name';
|
||||
const k8sDeploymentNameKey = 'k8s.deployment.name';
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sPodCpuUtilizationKey = getKey(
|
||||
'k8s.pod.cpu.usage',
|
||||
'k8s_pod_cpu_usage',
|
||||
);
|
||||
const k8sContainerCpuRequestKey = getKey(
|
||||
'k8s.container.cpu_request',
|
||||
'k8s_container_cpu_request',
|
||||
);
|
||||
const k8sPodMemoryUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
const k8sContainerMemoryRequestKey = getKey(
|
||||
'k8s.container.memory_request',
|
||||
'k8s_container_memory_request',
|
||||
);
|
||||
const k8sPodMemoryWorkingSetKey = getKey(
|
||||
'k8s.pod.memory.working_set',
|
||||
'k8s_pod_memory_working_set',
|
||||
);
|
||||
const k8sPodMemoryRssKey = getKey('k8s.pod.memory.rss', 'k8s_pod_memory_rss');
|
||||
const k8sPodNetworkIoKey = getKey('k8s.pod.network.io', 'k8s_pod_network_io');
|
||||
const k8sPodNetworkErrorsKey = getKey(
|
||||
'k8s.pod.network.errors',
|
||||
'k8s_pod_network_errors',
|
||||
);
|
||||
const k8sStatefulsetCurrentPodsKey = getKey(
|
||||
'k8s.statefulset.current_pods',
|
||||
'k8s_statefulset_current_pods',
|
||||
);
|
||||
const k8sStatefulsetDesiredPodsKey = getKey(
|
||||
'k8s.statefulset.desired_pods',
|
||||
'k8s_statefulset_desired_pods',
|
||||
);
|
||||
const k8sStatefulsetUpdatedPodsKey = getKey(
|
||||
'k8s.statefulset.updated_pods',
|
||||
'k8s_statefulset_updated_pods',
|
||||
);
|
||||
const k8sReplicasetDesiredKey = getKey(
|
||||
'k8s.replicaset.desired',
|
||||
'k8s_replicaset_desired',
|
||||
);
|
||||
const k8sReplicasetAvailableKey = getKey(
|
||||
'k8s.replicaset.available',
|
||||
'k8s_replicaset_available',
|
||||
);
|
||||
const k8sDaemonsetDesiredScheduledNamespacesKey = getKey(
|
||||
'k8s.daemonset.desired.scheduled.namespaces',
|
||||
'k8s_daemonset_desired_scheduled_namespaces',
|
||||
);
|
||||
const k8sDaemonsetCurrentScheduledNamespacesKey = getKey(
|
||||
'k8s.daemonset.current.scheduled.namespaces',
|
||||
'k8s_daemonset_current_scheduled_namespaces',
|
||||
);
|
||||
const k8sDaemonsetReadyNamespacesKey = getKey(
|
||||
'k8s.daemonset.ready.namespaces',
|
||||
'k8s_daemonset_ready_namespaces',
|
||||
);
|
||||
const k8sDaemonsetMisscheduledNamespacesKey = getKey(
|
||||
'k8s.daemonset.misscheduled.namespaces',
|
||||
'k8s_daemonset_misscheduled_namespaces',
|
||||
);
|
||||
const k8sDeploymentDesiredKey = getKey(
|
||||
'k8s.deployment.desired',
|
||||
'k8s_deployment_desired',
|
||||
);
|
||||
const k8sDeploymentAvailableKey = getKey(
|
||||
'k8s.deployment.available',
|
||||
'k8s_deployment_available',
|
||||
);
|
||||
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
const k8sStatefulsetNameKey = getKey(
|
||||
'k8s.statefulset.name',
|
||||
'k8s_statefulset_name',
|
||||
);
|
||||
const k8sReplicasetNameKey = getKey(
|
||||
'k8s.replicaset.name',
|
||||
'k8s_replicaset_name',
|
||||
);
|
||||
const k8sDaemonsetNameKey = getKey('k8s.daemonset.name', 'k8s_daemonset_name');
|
||||
const k8sDeploymentNameKey = getKey(
|
||||
'k8s.deployment.name',
|
||||
'k8s_deployment_name',
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -122,7 +122,7 @@ export const getK8sNamespacesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sNamespacesRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sNamespacesData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sNamespacesData['meta']> = {
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
};
|
||||
@@ -137,8 +137,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof namespace.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof namespace.meta;
|
||||
const value = namespace.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -25,6 +25,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -130,6 +132,11 @@ function K8sNodesList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sNodesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -213,10 +220,15 @@ function K8sNodesList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sNodesList(fetchGroupedByRowDataQuery as K8sNodesListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sNodesList(
|
||||
fetchGroupedByRowDataQuery as K8sNodesListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -224,7 +236,10 @@ function K8sNodesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NODES),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NODES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -305,6 +320,8 @@ function K8sNodesList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -54,40 +54,88 @@ export const getNodeMetricsQueryPayload = (
|
||||
node: K8sNodesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sNodeCpuUtilizationKey = 'k8s.node.cpu.usage';
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sNodeCpuUtilizationKey = getKey(
|
||||
'k8s.node.cpu.usage',
|
||||
'k8s_node_cpu_usage',
|
||||
);
|
||||
|
||||
const k8sNodeAllocatableCpuKey = 'k8s.node.allocatable_cpu';
|
||||
const k8sNodeAllocatableCpuKey = getKey(
|
||||
'k8s.node.allocatable_cpu',
|
||||
'k8s_node_allocatable_cpu',
|
||||
);
|
||||
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuRequestKey = getKey(
|
||||
'k8s.container.cpu_request',
|
||||
'k8s_container_cpu_request',
|
||||
);
|
||||
|
||||
const k8sNodeMemoryUsageKey = 'k8s.node.memory.usage';
|
||||
const k8sNodeMemoryUsageKey = getKey(
|
||||
'k8s.node.memory.usage',
|
||||
'k8s_node_memory_usage',
|
||||
);
|
||||
|
||||
const k8sNodeAllocatableMemoryKey = 'k8s.node.allocatable_memory';
|
||||
const k8sNodeAllocatableMemoryKey = getKey(
|
||||
'k8s.node.allocatable_memory',
|
||||
'k8s_node_allocatable_memory',
|
||||
);
|
||||
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemoryRequestKey = getKey(
|
||||
'k8s.container.memory_request',
|
||||
'k8s_container_memory_request',
|
||||
);
|
||||
|
||||
const k8sNodeMemoryWorkingSetKey = 'k8s.node.memory.working_set';
|
||||
const k8sNodeMemoryWorkingSetKey = getKey(
|
||||
'k8s.node.memory.working_set',
|
||||
'k8s_node_memory_working_set',
|
||||
);
|
||||
|
||||
const k8sNodeMemoryRssKey = 'k8s.node.memory.rss';
|
||||
const k8sNodeMemoryRssKey = getKey(
|
||||
'k8s.node.memory.rss',
|
||||
'k8s_node_memory_rss',
|
||||
);
|
||||
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
const k8sPodCpuUtilizationKey = getKey(
|
||||
'k8s.pod.cpu.usage',
|
||||
'k8s_pod_cpu_usage',
|
||||
);
|
||||
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sPodMemoryUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
|
||||
const k8sNodeNetworkErrorsKey = 'k8s.node.network.errors';
|
||||
const k8sNodeNetworkErrorsKey = getKey(
|
||||
'k8s.node.network.errors',
|
||||
'k8s_node_network_errors',
|
||||
);
|
||||
|
||||
const k8sNodeNetworkIoKey = 'k8s.node.network.io';
|
||||
const k8sNodeNetworkIoKey = getKey(
|
||||
'k8s.node.network.io',
|
||||
'k8s_node_network_io',
|
||||
);
|
||||
|
||||
const k8sNodeFilesystemUsageKey = 'k8s.node.filesystem.usage';
|
||||
const k8sNodeFilesystemUsageKey = getKey(
|
||||
'k8s.node.filesystem.usage',
|
||||
'k8s_node_filesystem_usage',
|
||||
);
|
||||
|
||||
const k8sNodeFilesystemCapacityKey = 'k8s.node.filesystem.capacity';
|
||||
const k8sNodeFilesystemCapacityKey = getKey(
|
||||
'k8s.node.filesystem.capacity',
|
||||
'k8s_node_filesystem_capacity',
|
||||
);
|
||||
|
||||
const k8sNodeFilesystemAvailableKey = 'k8s.node.filesystem.available';
|
||||
const k8sNodeFilesystemAvailableKey = getKey(
|
||||
'k8s.node.filesystem.available',
|
||||
'k8s_node_filesystem_available',
|
||||
);
|
||||
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
const k8sNodeNameKey = getKey('k8s.node.name', 'k8s_node_name');
|
||||
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -152,7 +152,7 @@ export const getK8sNodesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sNodesRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sNodesData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sNodesData['meta']> = {
|
||||
'k8s.node.name': 'k8s_node_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
'k8s.node.uid': 'k8s_node_uid',
|
||||
@@ -168,8 +168,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof node.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof node.meta;
|
||||
|
||||
const value = node.meta[metaKey];
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -118,13 +120,21 @@ function K8sPodsList({
|
||||
[currentQuery?.builder?.queryData],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
isLoading: isLoadingGroupByFilters,
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.PODS),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.PODS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -234,6 +244,8 @@ function K8sPodsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
@@ -311,10 +323,15 @@ function K8sPodsList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sPodsList(fetchGroupedByRowDataQuery as K8sPodsListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sPodsList(
|
||||
fetchGroupedByRowDataQuery as K8sPodsListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const podsData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
const totalCount = data?.payload?.data?.total || 0;
|
||||
|
||||
@@ -66,56 +66,116 @@ export const getPodMetricsQueryPayload = (
|
||||
pod: K8sPodsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sContainerNameKey = 'k8s.container.name';
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sContainerNameKey = getKey('k8s.container.name', 'k8s_container_name');
|
||||
|
||||
const k8sPodCpuUtilKey = 'k8s.pod.cpu.usage';
|
||||
const k8sPodCpuUtilKey = getKey('k8s.pod.cpu.usage', 'k8s_pod_cpu_usage');
|
||||
|
||||
const k8sPodCpuReqUtilKey = 'k8s.pod.cpu_request_utilization';
|
||||
const k8sPodCpuReqUtilKey = getKey(
|
||||
'k8s.pod.cpu_request_utilization',
|
||||
'k8s_pod_cpu_request_utilization',
|
||||
);
|
||||
|
||||
const k8sPodCpuLimitUtilKey = 'k8s.pod.cpu_limit_utilization';
|
||||
const k8sPodCpuLimitUtilKey = getKey(
|
||||
'k8s.pod.cpu_limit_utilization',
|
||||
'k8s_pod_cpu_limit_utilization',
|
||||
);
|
||||
|
||||
const k8sPodMemUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sPodMemUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
|
||||
const k8sPodMemReqUtilKey = 'k8s.pod.memory_request_utilization';
|
||||
const k8sPodMemReqUtilKey = getKey(
|
||||
'k8s.pod.memory_request_utilization',
|
||||
'k8s_pod_memory_request_utilization',
|
||||
);
|
||||
|
||||
const k8sPodMemLimitUtilKey = 'k8s.pod.memory_limit_utilization';
|
||||
const k8sPodMemLimitUtilKey = getKey(
|
||||
'k8s.pod.memory_limit_utilization',
|
||||
'k8s_pod_memory_limit_utilization',
|
||||
);
|
||||
|
||||
const k8sPodMemRssKey = 'k8s.pod.memory.rss';
|
||||
const k8sPodMemRssKey = getKey('k8s.pod.memory.rss', 'k8s_pod_memory_rss');
|
||||
|
||||
const k8sPodMemWorkingSetKey = 'k8s.pod.memory.working_set';
|
||||
const k8sPodMemWorkingSetKey = getKey(
|
||||
'k8s.pod.memory.working_set',
|
||||
'k8s_pod_memory_working_set',
|
||||
);
|
||||
|
||||
const k8sPodMemMajorPFKey = 'k8s.pod.memory.major_page_faults';
|
||||
const k8sPodMemMajorPFKey = getKey(
|
||||
'k8s.pod.memory.major_page_faults',
|
||||
'k8s_pod_memory_major_page_faults',
|
||||
);
|
||||
|
||||
const containerCpuUtilKey = 'container.cpu.usage';
|
||||
const containerCpuUtilKey = getKey(
|
||||
'container.cpu.usage',
|
||||
'container_cpu_usage',
|
||||
);
|
||||
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuRequestKey = getKey(
|
||||
'k8s.container.cpu_request',
|
||||
'k8s_container_cpu_request',
|
||||
);
|
||||
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerCpuLimitKey = getKey(
|
||||
'k8s.container.cpu_limit',
|
||||
'k8s_container_cpu_limit',
|
||||
);
|
||||
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sContainerMemoryLimitKey = getKey(
|
||||
'k8s.container.memory_limit',
|
||||
'k8s_container_memory_limit',
|
||||
);
|
||||
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemoryRequestKey = getKey(
|
||||
'k8s.container.memory_request',
|
||||
'k8s_container_memory_request',
|
||||
);
|
||||
|
||||
const containerMemUsageKey = 'container.memory.usage';
|
||||
const containerMemUsageKey = getKey(
|
||||
'container.memory.usage',
|
||||
'container_memory_usage',
|
||||
);
|
||||
|
||||
const containerMemWorkingSetKey = 'container.memory.working_set';
|
||||
const containerMemWorkingSetKey = getKey(
|
||||
'container.memory.working_set',
|
||||
'container_memory_working_set',
|
||||
);
|
||||
|
||||
const containerMemRssKey = 'container.memory.rss';
|
||||
const containerMemRssKey = getKey(
|
||||
'container.memory.rss',
|
||||
'container_memory_rss',
|
||||
);
|
||||
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkIoKey = getKey('k8s.pod.network.io', 'k8s_pod_network_io');
|
||||
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sPodNetworkErrorsKey = getKey(
|
||||
'k8s.pod.network.errors',
|
||||
'k8s_pod_network_errors',
|
||||
);
|
||||
|
||||
const k8sPodFilesystemCapacityKey = 'k8s.pod.filesystem.capacity';
|
||||
const k8sPodFilesystemCapacityKey = getKey(
|
||||
'k8s.pod.filesystem.capacity',
|
||||
'k8s_pod_filesystem_capacity',
|
||||
);
|
||||
|
||||
const k8sPodFilesystemAvailableKey = 'k8s.pod.filesystem.available';
|
||||
const k8sPodFilesystemAvailableKey = getKey(
|
||||
'k8s.pod.filesystem.available',
|
||||
'k8s_pod_filesystem_available',
|
||||
);
|
||||
|
||||
const k8sPodFilesystemUsageKey = 'k8s.pod.filesystem.usage';
|
||||
const k8sPodFilesystemUsageKey = getKey(
|
||||
'k8s.pod.filesystem.usage',
|
||||
'k8s_pod_filesystem_usage',
|
||||
);
|
||||
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
|
||||
return [
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -137,6 +139,11 @@ function K8sStatefulSetsList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sStatefulSetsRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -226,6 +233,8 @@ function K8sStatefulSetsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -236,6 +245,7 @@ function K8sStatefulSetsList({
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.STATEFULSETS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
@@ -317,6 +327,8 @@ function K8sStatefulSetsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const statefulSetsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -38,25 +38,54 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
statefulSet: K8sStatefulSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sStatefulSetNameKey = 'k8s.statefulset.name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const k8sStatefulSetNameKey = dotMetricsEnabled
|
||||
? 'k8s.statefulset.name'
|
||||
: 'k8s_statefulset_name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
|
||||
const k8sPodCpuUtilKey = 'k8s.pod.cpu.usage';
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sPodCpuReqUtilKey = 'k8s.pod.cpu_request_utilization';
|
||||
const k8sPodCpuLimitUtilKey = 'k8s.pod.cpu_limit_utilization';
|
||||
const k8sPodCpuUtilKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const k8sContainerCpuRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sPodCpuReqUtilKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu_request_utilization'
|
||||
: 'k8s_pod_cpu_request_utilization';
|
||||
const k8sPodCpuLimitUtilKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu_limit_utilization'
|
||||
: 'k8s_pod_cpu_limit_utilization';
|
||||
|
||||
const k8sPodMemUsageKey = 'k8s.pod.memory.usage';
|
||||
const k8sContainerMemRequestKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sPodMemReqUtilKey = 'k8s.pod.memory_request_utilization';
|
||||
const k8sPodMemLimitUtilKey = 'k8s.pod.memory_limit_utilization';
|
||||
const k8sPodMemUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
const k8sContainerMemRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sPodMemReqUtilKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory_request_utilization'
|
||||
: 'k8s_pod_memory_request_utilization';
|
||||
const k8sPodMemLimitUtilKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory_limit_utilization'
|
||||
: 'k8s_pod_memory_limit_utilization';
|
||||
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -236,7 +236,7 @@ export const getK8sStatefulSetsListColumns = (
|
||||
return columnsConfig as ColumnType<K8sStatefulSetsRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sStatefulSetsData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sStatefulSetsData['meta']> = {
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.statefulset.name': 'k8s_statefulset_name',
|
||||
};
|
||||
@@ -251,7 +251,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
const metaKey = (dotToUnder[rawKey] ??
|
||||
rawKey) as keyof typeof statefulSet.meta;
|
||||
const value = statefulSet.meta[metaKey];
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
@@ -137,6 +139,11 @@ function K8sVolumesList({
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
selectedRowData: K8sVolumesRowData,
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
@@ -194,10 +201,15 @@ function K8sVolumesList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sVolumesList(fetchGroupedByRowDataQuery as K8sVolumesListPayload, {
|
||||
queryKey: ['volumeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
} = useGetK8sVolumesList(
|
||||
fetchGroupedByRowDataQuery as K8sVolumesListPayload,
|
||||
{
|
||||
queryKey: ['volumeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -205,7 +217,10 @@ function K8sVolumesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NODES),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NODES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -253,6 +268,8 @@ function K8sVolumesList({
|
||||
queryKey: ['volumeList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const volumesData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -34,17 +34,38 @@ export const getVolumeQueryPayload = (
|
||||
volume: K8sVolumesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sVolumeAvailableKey = 'k8s.volume.available';
|
||||
const k8sVolumeCapacityKey = 'k8s.volume.capacity';
|
||||
const k8sVolumeInodesUsedKey = 'k8s.volume.inodes.used';
|
||||
const k8sVolumeInodesKey = 'k8s.volume.inodes';
|
||||
const k8sVolumeInodesFreeKey = 'k8s.volume.inodes.free';
|
||||
const k8sVolumeTypeKey = 'k8s.volume.type';
|
||||
const k8sPVCNameKey = 'k8s.persistentvolumeclaim.name';
|
||||
const legendTemplate = '{{k8s.namespace.name}}-{{k8s.pod.name}}';
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const k8sVolumeAvailableKey = dotMetricsEnabled
|
||||
? 'k8s.volume.available'
|
||||
: 'k8s_volume_available';
|
||||
const k8sVolumeCapacityKey = dotMetricsEnabled
|
||||
? 'k8s.volume.capacity'
|
||||
: 'k8s_volume_capacity';
|
||||
const k8sVolumeInodesUsedKey = dotMetricsEnabled
|
||||
? 'k8s.volume.inodes.used'
|
||||
: 'k8s_volume_inodes_used';
|
||||
const k8sVolumeInodesKey = dotMetricsEnabled
|
||||
? 'k8s.volume.inodes'
|
||||
: 'k8s_volume_inodes';
|
||||
const k8sVolumeInodesFreeKey = dotMetricsEnabled
|
||||
? 'k8s.volume.inodes.free'
|
||||
: 'k8s_volume_inodes_free';
|
||||
const k8sVolumeTypeKey = dotMetricsEnabled
|
||||
? 'k8s.volume.type'
|
||||
: 'k8s_volume_type';
|
||||
const k8sPVCNameKey = dotMetricsEnabled
|
||||
? 'k8s.persistentvolumeclaim.name'
|
||||
: 'k8s_persistentvolumeclaim_name';
|
||||
const legendTemplate = dotMetricsEnabled
|
||||
? '{{k8s.namespace.name}}-{{k8s.pod.name}}'
|
||||
: '{{k8s_namespace_name}}-{{k8s_pod_name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -142,7 +142,7 @@ export const getK8sVolumesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sVolumesRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sVolumesData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sVolumesData['meta']> = {
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.node.name': 'k8s_node_name',
|
||||
'k8s.pod.name': 'k8s_pod_name',
|
||||
@@ -161,8 +161,7 @@ const getGroupByEle = (
|
||||
groupBy.forEach((group) => {
|
||||
const rawKey = group.key as string;
|
||||
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof volume.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof volume.meta;
|
||||
|
||||
const value = volume.meta[metaKey];
|
||||
|
||||
|
||||
@@ -36,7 +36,21 @@ export const K8sCategories = {
|
||||
VOLUMES: 'volumes',
|
||||
};
|
||||
|
||||
export const K8sEntityToAggregateAttributeMap = {
|
||||
export const underscoreMap = {
|
||||
[K8sCategory.HOSTS]: 'system_cpu_load_average_15m',
|
||||
[K8sCategory.PODS]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.NODES]: 'k8s_node_cpu_usage',
|
||||
[K8sCategory.NAMESPACES]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.CLUSTERS]: 'k8s_node_cpu_usage',
|
||||
[K8sCategory.DEPLOYMENTS]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.STATEFULSETS]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.DAEMONSETS]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.CONTAINERS]: 'k8s_pod_cpu_usage',
|
||||
[K8sCategory.JOBS]: 'k8s_job_desired_successful_pods',
|
||||
[K8sCategory.VOLUMES]: 'k8s_volume_capacity',
|
||||
};
|
||||
|
||||
export const dotMap = {
|
||||
[K8sCategory.HOSTS]: 'system.cpu.load_average.15m',
|
||||
[K8sCategory.PODS]: 'k8s.pod.cpu.usage',
|
||||
[K8sCategory.NODES]: 'k8s.node.cpu.usage',
|
||||
@@ -52,23 +66,51 @@ export const K8sEntityToAggregateAttributeMap = {
|
||||
|
||||
export function GetK8sEntityToAggregateAttribute(
|
||||
category: K8sCategory,
|
||||
dotMetricsEnabled: boolean,
|
||||
): string {
|
||||
return K8sEntityToAggregateAttributeMap[category];
|
||||
return dotMetricsEnabled ? dotMap[category] : underscoreMap[category];
|
||||
}
|
||||
|
||||
export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetPodsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const podKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const nodeKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const deploymentKey = dotMetricsEnabled
|
||||
? 'k8s.deployment.name'
|
||||
: 'k8s_deployment_name';
|
||||
const statefulsetKey = dotMetricsEnabled
|
||||
? 'k8s.statefulset.name'
|
||||
: 'k8s_statefulset_name';
|
||||
const daemonsetKey = dotMetricsEnabled
|
||||
? 'k8s.daemonset.name'
|
||||
: 'k8s_daemonset_name';
|
||||
const jobKey = dotMetricsEnabled ? 'k8s.job.name' : 'k8s_job_name';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
// Define aggregate attribute (metric) name
|
||||
const cpuUtilizationMetric = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Pod',
|
||||
attributeKey: {
|
||||
key: 'k8s.pod.name',
|
||||
key: podKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
id: 'k8s.pod.name--string--tag--true',
|
||||
id: `${podKey}--string--tag--true`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -76,13 +118,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.namespace.name--string--resource--false',
|
||||
id: `${namespaceKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -90,13 +132,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Node',
|
||||
attributeKey: {
|
||||
key: 'k8s.node.name',
|
||||
key: nodeKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.node.name--string--resource--false',
|
||||
id: `${nodeKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -104,13 +146,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource--false',
|
||||
id: `${clusterKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -118,13 +160,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Deployment',
|
||||
attributeKey: {
|
||||
key: 'k8s.deployment.name',
|
||||
key: deploymentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.deployment.name--string--resource--false',
|
||||
id: `${deploymentKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -132,13 +174,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Statefulset',
|
||||
attributeKey: {
|
||||
key: 'k8s.statefulset.name',
|
||||
key: statefulsetKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.statefulset.name--string--resource--false',
|
||||
id: `${statefulsetKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -146,13 +188,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'DaemonSet',
|
||||
attributeKey: {
|
||||
key: 'k8s.daemonset.name',
|
||||
key: daemonsetKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.daemonset.name--string--resource--false',
|
||||
id: `${daemonsetKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -160,13 +202,13 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Job',
|
||||
attributeKey: {
|
||||
key: 'k8s.job.name',
|
||||
key: jobKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.job.name--string--resource--false',
|
||||
id: `${jobKey}--string--resource--false`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -174,7 +216,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -183,19 +225,33 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetNodesQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
// Define attribute keys
|
||||
const nodeKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
|
||||
// Define aggregate metric name for node CPU utilization
|
||||
const cpuUtilMetric = dotMetricsEnabled
|
||||
? 'k8s.node.cpu.usage'
|
||||
: 'k8s_node_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Node Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.node.name',
|
||||
key: nodeKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.node.name--string--resource--true',
|
||||
id: `${nodeKey}--string--resource--true`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -203,13 +259,13 @@ export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource--true',
|
||||
id: `${clusterKey}--string--resource--true`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -217,7 +273,7 @@ export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -226,19 +282,32 @@ export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetNamespaceQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const cpuUtilMetric = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -246,13 +315,13 @@ export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -260,7 +329,7 @@ export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -269,19 +338,29 @@ export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetClustersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetClustersQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const cpuUtilMetric = dotMetricsEnabled
|
||||
? 'k8s.node.cpu.usage'
|
||||
: 'k8s_node_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -289,7 +368,7 @@ export function GetClustersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -298,16 +377,25 @@ export function GetClustersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetContainersQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const containerKey = dotMetricsEnabled
|
||||
? 'k8s.container.name'
|
||||
: 'k8s_container_name';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Container',
|
||||
attributeKey: {
|
||||
key: 'k8s.container.name',
|
||||
key: containerKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.container.name--string--resource',
|
||||
id: `${containerKey}--string--resource`,
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -315,7 +403,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -324,19 +412,35 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetVolumesQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const pvcKey = dotMetricsEnabled
|
||||
? 'k8s.persistentvolumeclaim.name'
|
||||
: 'k8s_persistentvolumeclaim_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const volumeMetric = dotMetricsEnabled
|
||||
? 'k8s.volume.capacity'
|
||||
: 'k8s_volume_capacity';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'PVC Volume Claim Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.persistentvolumeclaim.name',
|
||||
key: pvcKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.persistentvolumeclaim.name--string--resource',
|
||||
id: `${pvcKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
aggregateAttribute: volumeMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -344,13 +448,13 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
aggregateAttribute: volumeMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -358,13 +462,13 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
aggregateAttribute: volumeMetric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -372,7 +476,7 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -381,19 +485,33 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetDeploymentsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const deployKey = dotMetricsEnabled
|
||||
? 'k8s.deployment.name'
|
||||
: 'k8s_deployment_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const metric = dotMetricsEnabled ? 'k8s.pod.cpu.usage' : 'k8s_pod_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Deployment Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.deployment.name',
|
||||
key: deployKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.deployment.name--string--resource',
|
||||
id: `${deployKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -401,13 +519,13 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -415,13 +533,13 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -429,7 +547,7 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -438,19 +556,33 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetStatefulsetsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const ssKey = dotMetricsEnabled
|
||||
? 'k8s.statefulset.name'
|
||||
: 'k8s_statefulset_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const metric = dotMetricsEnabled ? 'k8s.pod.cpu.usage' : 'k8s_pod_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Statefulset Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.statefulset.name',
|
||||
key: ssKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.statefulset.name--string--resource',
|
||||
id: `${ssKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -458,13 +590,13 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -472,13 +604,13 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metric,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -486,7 +618,7 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -495,19 +627,35 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetDaemonsetsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const nameKey = dotMetricsEnabled
|
||||
? 'k8s.daemonset.name'
|
||||
: 'k8s_daemonset_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const metricName = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'DaemonSet Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.daemonset.name',
|
||||
key: nameKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.daemonset.name--string--resource--true',
|
||||
id: `${nameKey}--string--resource--true`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -515,12 +663,12 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -528,12 +676,12 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -541,7 +689,7 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -550,19 +698,33 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
export function GetJobsQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const nameKey = dotMetricsEnabled ? 'k8s.job.name' : 'k8s_job_name';
|
||||
const namespaceKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const clusterKey = dotMetricsEnabled ? 'k8s.cluster.name' : 'k8s_cluster_name';
|
||||
const metricName = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Job Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.job.name',
|
||||
key: nameKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: 'k8s.job.name--string--resource--true',
|
||||
id: `${nameKey}--string--resource--true`,
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -570,12 +732,12 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.namespace.name',
|
||||
key: namespaceKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -583,12 +745,12 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: 'k8s.cluster.name',
|
||||
key: clusterKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
aggregateAttribute: metricName,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -596,7 +758,7 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: 'deployment.environment',
|
||||
key: environmentKey,
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -299,7 +299,7 @@ export const getK8sPodsListColumns = (
|
||||
return updatedColumnsConfig as ColumnType<K8sPodsRowData>[];
|
||||
};
|
||||
|
||||
const attributeToMetaKey: Record<string, keyof K8sPodsData['meta']> = {
|
||||
const dotToUnder: Record<string, keyof K8sPodsData['meta']> = {
|
||||
'k8s.cronjob.name': 'k8s_cronjob_name',
|
||||
'k8s.daemonset.name': 'k8s_daemonset_name',
|
||||
'k8s.deployment.name': 'k8s_deployment_name',
|
||||
@@ -322,8 +322,7 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof pod.meta;
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof pod.meta;
|
||||
const value = pod.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -16,6 +16,8 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import {
|
||||
getHostQueryPayload,
|
||||
getNodeQueryPayload,
|
||||
@@ -50,12 +52,23 @@ function NodeMetrics({
|
||||
};
|
||||
}, [timestamp]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(() => {
|
||||
if (nodeName) {
|
||||
return getNodeQueryPayload(clusterName, nodeName, start, end);
|
||||
return getNodeQueryPayload(
|
||||
clusterName,
|
||||
nodeName,
|
||||
start,
|
||||
end,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
}
|
||||
return getHostQueryPayload(hostName, start, end);
|
||||
}, [nodeName, hostName, clusterName, start, end]);
|
||||
return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
|
||||
}, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
|
||||
|
||||
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -11,11 +11,13 @@ import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { getPodQueryPayload, podWidgetInfo } from './constants';
|
||||
|
||||
function PodMetrics({
|
||||
@@ -51,9 +53,14 @@ function PodMetrics({
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => getPodQueryPayload(clusterName, podName, start, end),
|
||||
[clusterName, end, podName, start],
|
||||
() => getPodQueryPayload(clusterName, podName, start, end, dotMetricsEnabled),
|
||||
[clusterName, end, podName, start, dotMetricsEnabled],
|
||||
);
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
|
||||
@@ -10,21 +10,48 @@ export const getPodQueryPayload = (
|
||||
podName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
const containerCpuUtilKey = 'container.cpu.usage';
|
||||
const containerMemUsageKey = 'container.memory.usage';
|
||||
const k8sContainerCpuReqKey = 'k8s.container.cpu_request';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
const k8sContainerMemReqKey = 'k8s.container.memory_request';
|
||||
const k8sContainerMemLimitKey = 'k8s.container.memory_limit';
|
||||
const k8sPodFsAvailKey = 'k8s.pod.filesystem.available';
|
||||
const k8sPodFsCapKey = 'k8s.pod.filesystem.capacity';
|
||||
const k8sPodNetIoKey = 'k8s.pod.network.io';
|
||||
const podLegendTemplate = '{{k8s.pod.name}}';
|
||||
const podLegendUsage = 'usage - {{k8s.pod.name}}';
|
||||
const podLegendLimit = 'limit - {{k8s.pod.name}}';
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const containerCpuUtilKey = dotMetricsEnabled
|
||||
? 'container.cpu.usage'
|
||||
: 'container_cpu_usage';
|
||||
const containerMemUsageKey = dotMetricsEnabled
|
||||
? 'container.memory.usage'
|
||||
: 'container_memory_usage';
|
||||
const k8sContainerCpuReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sContainerMemReqKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sPodFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.available'
|
||||
: 'k8s_pod_filesystem_available';
|
||||
const k8sPodFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.pod.filesystem.capacity'
|
||||
: 'k8s_pod_filesystem_capacity';
|
||||
const k8sPodNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const podLegendTemplate = dotMetricsEnabled
|
||||
? '{{k8s.pod.name}}'
|
||||
: '{{k8s_pod_name}}';
|
||||
const podLegendUsage = dotMetricsEnabled
|
||||
? 'usage - {{k8s.pod.name}}'
|
||||
: 'usage - {{k8s_pod_name}}';
|
||||
const podLegendLimit = dotMetricsEnabled
|
||||
? 'limit - {{k8s.pod.name}}'
|
||||
: 'limit - {{k8s_pod_name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1001,17 +1028,36 @@ export const getNodeQueryPayload = (
|
||||
nodeName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sClusterNameKey = 'k8s.cluster.name';
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
const k8sNodeCpuTimeKey = 'k8s.node.cpu.time';
|
||||
const k8sNodeAllocCpuKey = 'k8s.node.allocatable_cpu';
|
||||
const k8sNodeMemWsKey = 'k8s.node.memory.working_set';
|
||||
const k8sNodeAllocMemKey = 'k8s.node.allocatable_memory';
|
||||
const k8sNodeNetIoKey = 'k8s.node.network.io';
|
||||
const k8sNodeFsAvailKey = 'k8s.node.filesystem.available';
|
||||
const k8sNodeFsCapKey = 'k8s.node.filesystem.capacity';
|
||||
const podLegend = '{{k8s.node.name}}';
|
||||
const k8sClusterNameKey = dotMetricsEnabled
|
||||
? 'k8s.cluster.name'
|
||||
: 'k8s_cluster_name';
|
||||
const k8sNodeNameKey = dotMetricsEnabled ? 'k8s.node.name' : 'k8s_node_name';
|
||||
const k8sNodeCpuTimeKey = dotMetricsEnabled
|
||||
? 'k8s.node.cpu.time'
|
||||
: 'k8s_node_cpu_time';
|
||||
const k8sNodeAllocCpuKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_cpu'
|
||||
: 'k8s_node_allocatable_cpu';
|
||||
const k8sNodeMemWsKey = dotMetricsEnabled
|
||||
? 'k8s.node.memory.working_set'
|
||||
: 'k8s_node_memory_working_set';
|
||||
const k8sNodeAllocMemKey = dotMetricsEnabled
|
||||
? 'k8s.node.allocatable_memory'
|
||||
: 'k8s_node_allocatable_memory';
|
||||
const k8sNodeNetIoKey = dotMetricsEnabled
|
||||
? 'k8s.node.network.io'
|
||||
: 'k8s_node_network_io';
|
||||
const k8sNodeFsAvailKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.available'
|
||||
: 'k8s_node_filesystem_available';
|
||||
const k8sNodeFsCapKey = dotMetricsEnabled
|
||||
? 'k8s.node.filesystem.capacity'
|
||||
: 'k8s_node_filesystem_capacity';
|
||||
const podLegend = dotMetricsEnabled
|
||||
? '{{k8s.node.name}}'
|
||||
: '{{k8s_node_name}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1541,21 +1587,42 @@ export const getHostQueryPayload = (
|
||||
hostName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const hostNameKey = 'host.name';
|
||||
const cpuTimeKey = 'system.cpu.time';
|
||||
const memUsageKey = 'system.memory.usage';
|
||||
const load1mKey = 'system.cpu.load_average.1m';
|
||||
const load5mKey = 'system.cpu.load_average.5m';
|
||||
const load15mKey = 'system.cpu.load_average.15m';
|
||||
const netIoKey = 'system.network.io';
|
||||
const netPktsKey = 'system.network.packets';
|
||||
const netErrKey = 'system.network.errors';
|
||||
const netDropKey = 'system.network.dropped';
|
||||
const netConnKey = 'system.network.connections';
|
||||
const diskIoKey = 'system.disk.io';
|
||||
const diskOpTimeKey = 'system.disk.operation_time';
|
||||
const diskPendingKey = 'system.disk.pending_operations';
|
||||
const hostNameKey = dotMetricsEnabled ? 'host.name' : 'host_name';
|
||||
const cpuTimeKey = dotMetricsEnabled ? 'system.cpu.time' : 'system_cpu_time';
|
||||
const memUsageKey = dotMetricsEnabled
|
||||
? 'system.memory.usage'
|
||||
: 'system_memory_usage';
|
||||
const load1mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.1m'
|
||||
: 'system_cpu_load_average_1m';
|
||||
const load5mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.5m'
|
||||
: 'system_cpu_load_average_5m';
|
||||
const load15mKey = dotMetricsEnabled
|
||||
? 'system.cpu.load_average.15m'
|
||||
: 'system_cpu_load_average_15m';
|
||||
const netIoKey = dotMetricsEnabled ? 'system.network.io' : 'system_network_io';
|
||||
const netPktsKey = dotMetricsEnabled
|
||||
? 'system.network.packets'
|
||||
: 'system_network_packets';
|
||||
const netErrKey = dotMetricsEnabled
|
||||
? 'system.network.errors'
|
||||
: 'system_network_errors';
|
||||
const netDropKey = dotMetricsEnabled
|
||||
? 'system.network.dropped'
|
||||
: 'system_network_dropped';
|
||||
const netConnKey = dotMetricsEnabled
|
||||
? 'system.network.connections'
|
||||
: 'system_network_connections';
|
||||
const diskIoKey = dotMetricsEnabled ? 'system.disk.io' : 'system_disk_io';
|
||||
const diskOpTimeKey = dotMetricsEnabled
|
||||
? 'system.disk.operation_time'
|
||||
: 'system_disk_operation_time';
|
||||
const diskPendingKey = dotMetricsEnabled
|
||||
? 'system.disk.pending_operations'
|
||||
: 'system_disk_pending_operations';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ export const databaseCallsRPS = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallsRPSProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -32,7 +33,7 @@ export const databaseCallsRPS = ({
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: WidgetKeys.DbSystem,
|
||||
key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
|
||||
type: 'tag',
|
||||
},
|
||||
];
|
||||
@@ -41,7 +42,9 @@ export const databaseCallsRPS = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -72,6 +75,7 @@ export const databaseCallsRPS = ({
|
||||
export const databaseCallsAvgDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozDbLatencySum,
|
||||
@@ -88,7 +92,9 @@ export const databaseCallsAvgDuration = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ export const externalCallErrorPercent = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||
@@ -48,7 +49,9 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -58,7 +61,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -71,7 +74,9 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -115,6 +120,7 @@ export const externalCallErrorPercent = ({
|
||||
export const externalCallDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -135,7 +141,9 @@ export const externalCallDuration = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -175,6 +183,7 @@ export const externalCallRpsByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -189,7 +198,9 @@ export const externalCallRpsByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -220,6 +231,7 @@ export const externalCallDurationByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -239,7 +251,9 @@ export const externalCallDurationByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -37,10 +37,15 @@ export const latency = ({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable = false,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}: LatencyProps): QueryBuilderData => {
|
||||
const signozLatencyBucketMetrics = WidgetKeys.SignozLatencyBucket;
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
|
||||
const signozMetricsServiceName = WidgetKeys.OTelServiceName;
|
||||
const signozMetricsServiceName = dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm;
|
||||
const newAutoCompleteData: BaseAutocompleteData = {
|
||||
key: isSpanMetricEnable
|
||||
? signozLatencyBucketMetrics
|
||||
@@ -282,21 +287,28 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
threashold,
|
||||
delta,
|
||||
metricsBuckets,
|
||||
dotMetricsEnabled,
|
||||
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -305,7 +317,9 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -329,7 +343,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -349,7 +363,9 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -383,7 +399,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -393,7 +409,9 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -456,10 +474,13 @@ export const operationPerSec = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
},
|
||||
@@ -470,7 +491,9 @@ export const operationPerSec = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -511,6 +534,7 @@ export const errorPercentage = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozCallsTotal,
|
||||
@@ -529,7 +553,9 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -549,7 +575,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.StatusCode,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -563,7 +589,9 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -21,9 +21,12 @@ import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
|
||||
|
||||
export const topOperationQueries = ({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}: TopOperationQueryFactoryProps): QueryBuilderData => {
|
||||
const latencyAutoCompleteData: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -35,7 +38,9 @@ export const topOperationQueries = ({
|
||||
};
|
||||
|
||||
const numOfCallAutoCompleteData: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -44,7 +49,9 @@ export const topOperationQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -58,7 +65,9 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -68,7 +77,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: WidgetKeys.StatusCode,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -27,6 +27,8 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { GraphTitle, MENU_ITEMS, SERVICE_CHART_ID } from '../constant';
|
||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||
import { Card, GraphContainer, Row } from '../styles';
|
||||
@@ -81,7 +83,12 @@ function DBCall(): JSX.Element {
|
||||
[queries],
|
||||
);
|
||||
|
||||
const legend = '{{db.system}}';
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
() =>
|
||||
@@ -93,6 +100,7 @@ function DBCall(): JSX.Element {
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -103,7 +111,7 @@ function DBCall(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.dbCallsRPS,
|
||||
fillSpans: false,
|
||||
}),
|
||||
[servicename, tagFilterItems, legend],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled, legend],
|
||||
);
|
||||
const databaseCallsAverageDurationWidget = useMemo(
|
||||
() =>
|
||||
@@ -114,6 +122,7 @@ function DBCall(): JSX.Element {
|
||||
builder: databaseCallsAvgDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -124,7 +133,7 @@ function DBCall(): JSX.Element {
|
||||
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
@@ -142,7 +151,7 @@ function DBCall(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
|
||||
@@ -29,6 +29,8 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { GraphTitle, legend, MENU_ITEMS } from '../constant';
|
||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||
import { Card, GraphContainer, Row } from '../styles';
|
||||
@@ -76,6 +78,11 @@ function External(): JSX.Element {
|
||||
handleNonInQueryRange(resourceAttributesToTagFilterItems(queries)) || [],
|
||||
[queries],
|
||||
);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const externalCallErrorWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -86,6 +93,7 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -95,7 +103,7 @@ function External(): JSX.Element {
|
||||
yAxisUnit: '%',
|
||||
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
@@ -112,6 +120,7 @@ function External(): JSX.Element {
|
||||
builder: externalCallDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -122,7 +131,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const errorApmToTraceQuery = useGetAPMToTracesQueries({
|
||||
@@ -156,7 +165,7 @@ function External(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -179,6 +188,7 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -189,7 +199,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const externalCallDurationAddressWidget = useMemo(
|
||||
@@ -202,6 +212,7 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -212,7 +223,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems],
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const apmToTraceQuery = useGetAPMToTracesQueries({
|
||||
|
||||
@@ -93,11 +93,15 @@ function Application(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[handleSetTimeStamp],
|
||||
);
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const logEventCalledRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -155,6 +159,7 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -164,7 +169,7 @@ function Application(): JSX.Element {
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const errorPercentageWidget = useMemo(
|
||||
@@ -177,6 +182,7 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -187,7 +193,7 @@ function Application(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
|
||||
@@ -20,6 +20,8 @@ import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/Me
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { IServiceName } from '../../types';
|
||||
import { ApDexMetricsProps } from './types';
|
||||
|
||||
@@ -34,6 +36,10 @@ function ApDexMetrics({
|
||||
}: ApDexMetricsProps): JSX.Element {
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
const apDexMetricsWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -47,6 +53,7 @@ function ApDexMetrics({
|
||||
threashold: thresholdValue || 0,
|
||||
delta: delta || false,
|
||||
metricsBuckets: metricsBuckets || [],
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -72,6 +79,7 @@ function ApDexMetrics({
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import Spinner from 'components/Spinner';
|
||||
import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { WidgetKeys } from '../../../constant';
|
||||
import { IServiceName } from '../../types';
|
||||
import ApDexMetrics from './ApDexMetrics';
|
||||
@@ -18,8 +20,17 @@ function ApDexMetricsApplication({
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
|
||||
const { data, isLoading, error } = useGetMetricMeta(
|
||||
WidgetKeys.SignozLatencyBucket,
|
||||
signozLatencyBucketMetrics,
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
@@ -56,6 +56,10 @@ function ServiceOverview({
|
||||
[isSpanMetricEnable, queries],
|
||||
);
|
||||
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const latencyWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -67,6 +71,7 @@ function ServiceOverview({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -76,7 +81,13 @@ function ServiceOverview({
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
}),
|
||||
[isSpanMetricEnable, servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
[
|
||||
isSpanMetricEnable,
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
const isQueryEnabled =
|
||||
|
||||
@@ -18,6 +18,8 @@ import { EQueryType } from 'types/common/dashboard';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { IServiceName } from '../types';
|
||||
import { title } from './config';
|
||||
import ColumnWithLink from './TableRenderer/ColumnWithLink';
|
||||
@@ -40,6 +42,11 @@ function TopOperationMetrics(): JSX.Element {
|
||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const keyOperationWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -48,13 +55,14 @@ function TopOperationMetrics(): JSX.Element {
|
||||
promql: [],
|
||||
builder: topOperationQueries({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
panelTypes: PANEL_TYPES.TABLE,
|
||||
}),
|
||||
[servicename],
|
||||
[servicename, dotMetricsEnabled],
|
||||
);
|
||||
|
||||
const updatedQuery = updateStepInterval(keyOperationWidget.query);
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface IServiceName {
|
||||
|
||||
export interface TopOperationQueryFactoryProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
@@ -19,6 +20,7 @@ export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
export interface ExternalCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderQueriesProps {
|
||||
@@ -50,6 +52,7 @@ export interface OperationPerSecProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
topLevelOperations: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface LatencyProps {
|
||||
@@ -57,6 +60,7 @@ export interface LatencyProps {
|
||||
tagFilterItems: TagFilterItem[];
|
||||
isSpanMetricEnable?: boolean;
|
||||
topLevelOperationsRoute: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ApDexProps {
|
||||
@@ -74,4 +78,5 @@ export interface TableRendererProps {
|
||||
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||
delta: boolean;
|
||||
metricsBuckets: number[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user