mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-14 21:32:04 +00:00
Compare commits
2 Commits
sso-mappin
...
remote-dot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fa8cbecad | ||
|
|
2c948ef9f6 |
@@ -67,14 +67,6 @@ 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,14 +18,3 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"@signozhq/popover": "0.0.0",
|
||||
"@signozhq/resizable": "0.0.0",
|
||||
"@signozhq/sonner": "0.1.0",
|
||||
"@signozhq/switch": "0.0.2",
|
||||
"@signozhq/table": "0.3.7",
|
||||
"@signozhq/tooltip": "0.0.2",
|
||||
"@tanstack/react-table": "8.20.6",
|
||||
|
||||
@@ -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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sClustersListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -64,41 +64,39 @@ export const getK8sClustersList = async (
|
||||
props: K8sClustersListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sDaemonSetsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,42 +71,39 @@ export const getK8sDaemonSetsList = async (
|
||||
props: K8sDaemonSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
// 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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sDeploymentsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,41 +71,39 @@ export const getK8sDeploymentsList = async (
|
||||
props: K8sDeploymentsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sJobsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -71,41 +71,39 @@ export const getK8sJobsList = async (
|
||||
props: K8sJobsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sNamespacesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -62,41 +62,39 @@ export const getK8sNamespacesList = async (
|
||||
props: K8sNamespacesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sNodesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -66,41 +66,39 @@ export const getK8sNodesList = async (
|
||||
props: K8sNodesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sPodsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -102,41 +102,39 @@ export const getK8sPodsList = async (
|
||||
props: K8sPodsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sVolumesListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -86,39 +86,37 @@ export const getK8sVolumesList = async (
|
||||
props: K8sVolumesListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
// Prepare filters
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 { UnderscoreToDotMap } from '../utils';
|
||||
import { AttributeKeyMap } from '../utils';
|
||||
|
||||
export interface K8sStatefulSetsListPayload {
|
||||
filters: TagFilter;
|
||||
@@ -69,39 +69,37 @@ export const getK8sStatefulSetsList = async (
|
||||
props: K8sStatefulSetsListPayload,
|
||||
signal?: AbortSignal,
|
||||
headers?: Record<string, string>,
|
||||
dotMetricsEnabled = false,
|
||||
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
|
||||
try {
|
||||
// Prepare filters
|
||||
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);
|
||||
}
|
||||
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) {
|
||||
return acc;
|
||||
},
|
||||
[] as typeof props.filters.items,
|
||||
),
|
||||
},
|
||||
}
|
||||
: props;
|
||||
}
|
||||
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;
|
||||
|
||||
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 UnderscoreToDotMap: Record<string, string> = {
|
||||
export const AttributeKeyMap: Record<string, string> = {
|
||||
k8s_cluster_name: 'k8s.cluster.name',
|
||||
k8s_cluster_uid: 'k8s.cluster.uid',
|
||||
k8s_namespace_name: 'k8s.namespace.name',
|
||||
|
||||
19
frontend/src/api/v1/domains/id/delete.ts
Normal file
19
frontend/src/api/v1/domains/id/delete.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
|
||||
const deleteDomain = async (id: string): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete<null>(`/domains/${id}`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteDomain;
|
||||
25
frontend/src/api/v1/domains/id/put.ts
Normal file
25
frontend/src/api/v1/domains/id/put.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { UpdatableAuthDomain } from 'types/api/v1/domains/put';
|
||||
|
||||
const put = async (
|
||||
props: UpdatableAuthDomain,
|
||||
): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.put<RawSuccessResponse<null>>(
|
||||
`/domains/${props.id}`,
|
||||
{ config: props.config },
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default put;
|
||||
24
frontend/src/api/v1/domains/list.ts
Normal file
24
frontend/src/api/v1/domains/list.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { GettableAuthDomain } from 'types/api/v1/domains/list';
|
||||
|
||||
const listAllDomain = async (): Promise<
|
||||
SuccessResponseV2<GettableAuthDomain[]>
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get<RawSuccessResponse<GettableAuthDomain[]>>(
|
||||
`/domains`,
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default listAllDomain;
|
||||
26
frontend/src/api/v1/domains/post.ts
Normal file
26
frontend/src/api/v1/domains/post.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, RawSuccessResponse, SuccessResponseV2 } from 'types/api';
|
||||
import { GettableAuthDomain } from 'types/api/v1/domains/list';
|
||||
import { PostableAuthDomain } from 'types/api/v1/domains/post';
|
||||
|
||||
const post = async (
|
||||
props: PostableAuthDomain,
|
||||
): Promise<SuccessResponseV2<GettableAuthDomain>> => {
|
||||
try {
|
||||
const response = await axios.post<RawSuccessResponse<GettableAuthDomain>>(
|
||||
`/domains`,
|
||||
props,
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default post;
|
||||
1
frontend/src/auto-import-registry.d.ts
vendored
1
frontend/src/auto-import-registry.d.ts
vendored
@@ -23,6 +23,5 @@ import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/sonner';
|
||||
import '@signozhq/switch';
|
||||
import '@signozhq/table';
|
||||
import '@signozhq/tooltip';
|
||||
|
||||
@@ -23,9 +23,6 @@ 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 {
|
||||
@@ -50,11 +47,6 @@ 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,
|
||||
@@ -69,14 +61,8 @@ function Metrics({
|
||||
});
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() =>
|
||||
getHostQueryPayload(
|
||||
hostName,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
[hostName, timeRange.startTime, timeRange.endTime, dotMetricsEnabled],
|
||||
() => getHostQueryPayload(hostName, timeRange.startTime, timeRange.endTime),
|
||||
[hostName, timeRange.startTime, timeRange.endTime],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -8,5 +8,4 @@ export enum FeatureKeys {
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
ONBOARDING_V3 = 'onboarding_v3',
|
||||
DOT_METRICS_ENABLED = 'dot_metrics_enabled',
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ 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,
|
||||
@@ -415,11 +413,6 @@ 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,
|
||||
@@ -455,7 +448,7 @@ function AllErrors(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!isUndefined(errorCountResponse.data?.payload)) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('Exception: List page visited', {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useCallback } from 'react';
|
||||
import ChartWrapper from 'container/DashboardContainer/visualization/charts/ChartWrapper/ChartWrapper';
|
||||
import BarChartTooltip from 'lib/uPlotV2/components/Tooltip/BarChartTooltip';
|
||||
import {
|
||||
BarTooltipProps,
|
||||
TooltipRenderArgs,
|
||||
} from 'lib/uPlotV2/components/types';
|
||||
|
||||
import { useBarChartStacking } from '../../hooks/useBarChartStacking';
|
||||
import { BarChartProps } from '../types';
|
||||
|
||||
export default function BarChart(props: BarChartProps): JSX.Element {
|
||||
const { children, isStackedBarChart, config, data, ...rest } = props;
|
||||
|
||||
const chartData = useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart,
|
||||
config,
|
||||
});
|
||||
|
||||
const renderTooltip = useCallback(
|
||||
(props: TooltipRenderArgs): React.ReactNode => {
|
||||
const tooltipProps: BarTooltipProps = {
|
||||
...props,
|
||||
timezone: rest.timezone,
|
||||
yAxisUnit: rest.yAxisUnit,
|
||||
decimalPrecision: rest.decimalPrecision,
|
||||
isStackedBarChart: isStackedBarChart,
|
||||
};
|
||||
return <BarChartTooltip {...tooltipProps} />;
|
||||
},
|
||||
[rest.timezone, rest.yAxisUnit, rest.decimalPrecision, isStackedBarChart],
|
||||
);
|
||||
|
||||
return (
|
||||
<ChartWrapper
|
||||
{...rest}
|
||||
config={config}
|
||||
data={chartData}
|
||||
renderTooltip={renderTooltip}
|
||||
>
|
||||
{children}
|
||||
</ChartWrapper>
|
||||
);
|
||||
}
|
||||
@@ -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 stackSeries(
|
||||
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,125 @@
|
||||
import {
|
||||
MutableRefObject,
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { has } from 'lodash-es';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { stackSeries } from '../charts/utils/stackSeriesUtils';
|
||||
|
||||
/** Returns true if the series at the given index is hidden (e.g. via legend toggle). */
|
||||
function isSeriesHidden(plot: uPlot, seriesIndex: number): boolean {
|
||||
return !plot.series[seriesIndex]?.show;
|
||||
}
|
||||
|
||||
function canApplyStacking(
|
||||
unstackedData: uPlot.AlignedData | null,
|
||||
plot: uPlot,
|
||||
isUpdating: boolean,
|
||||
): boolean {
|
||||
return (
|
||||
!isUpdating &&
|
||||
!!unstackedData &&
|
||||
!!plot.data &&
|
||||
unstackedData[0]?.length === plot.data[0]?.length
|
||||
);
|
||||
}
|
||||
|
||||
function setupStackingHooks(
|
||||
config: UPlotConfigBuilder,
|
||||
applyStackingToChart: (plot: uPlot) => void,
|
||||
isUpdatingRef: MutableRefObject<boolean>,
|
||||
): () => void {
|
||||
const onDataChange = (plot: uPlot): void => {
|
||||
if (!isUpdatingRef.current) {
|
||||
applyStackingToChart(plot);
|
||||
}
|
||||
};
|
||||
|
||||
const onSeriesVisibilityChange = (
|
||||
plot: uPlot,
|
||||
_seriesIdx: number | null,
|
||||
opts: uPlot.Series,
|
||||
): void => {
|
||||
if (!has(opts, 'focus')) {
|
||||
applyStackingToChart(plot);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSetDataHook = config.addHook('setData', onDataChange);
|
||||
const removeSetSeriesHook = config.addHook(
|
||||
'setSeries',
|
||||
onSeriesVisibilityChange,
|
||||
);
|
||||
|
||||
return (): void => {
|
||||
removeSetDataHook?.();
|
||||
removeSetSeriesHook?.();
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseBarChartStackingParams {
|
||||
data: uPlot.AlignedData;
|
||||
isStackedBarChart?: boolean;
|
||||
config: UPlotConfigBuilder | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles stacking for bar charts: computes initial stacked data and re-stacks
|
||||
* when data or series visibility changes (e.g. legend toggles).
|
||||
*/
|
||||
export function useBarChartStacking({
|
||||
data,
|
||||
isStackedBarChart = false,
|
||||
config,
|
||||
}: UseBarChartStackingParams): uPlot.AlignedData {
|
||||
// Store unstacked source data so uPlot hooks can access it (hooks run outside React's render cycle)
|
||||
const unstackedDataRef = useRef<uPlot.AlignedData | null>(null);
|
||||
unstackedDataRef.current = isStackedBarChart ? data : null;
|
||||
|
||||
// Prevents re-entrant calls when we update chart data (avoids infinite loop in setData hook)
|
||||
const isUpdatingChartRef = useRef(false);
|
||||
|
||||
const chartData = useMemo((): uPlot.AlignedData => {
|
||||
if (!isStackedBarChart || !data || data.length < 2) {
|
||||
return data;
|
||||
}
|
||||
const noSeriesHidden = (): boolean => false; // include all series in initial stack
|
||||
const { data: stacked } = stackSeries(data, noSeriesHidden);
|
||||
return stacked;
|
||||
}, [data, isStackedBarChart]);
|
||||
|
||||
const applyStackingToChart = useCallback((plot: uPlot): void => {
|
||||
const unstacked = unstackedDataRef.current;
|
||||
if (
|
||||
!unstacked ||
|
||||
!canApplyStacking(unstacked, plot, isUpdatingChartRef.current)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldExcludeSeries = (idx: number): boolean =>
|
||||
isSeriesHidden(plot, idx);
|
||||
const { data: stacked, bands } = stackSeries(unstacked, shouldExcludeSeries);
|
||||
|
||||
plot.delBand(null);
|
||||
bands.forEach((band: uPlot.Band) => plot.addBand(band));
|
||||
|
||||
isUpdatingChartRef.current = true;
|
||||
plot.setData(stacked);
|
||||
isUpdatingChartRef.current = false;
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!isStackedBarChart || !config) {
|
||||
return undefined;
|
||||
}
|
||||
return setupStackingHooks(config, applyStackingToChart, isUpdatingChartRef);
|
||||
}, [isStackedBarChart, config, applyStackingToChart]);
|
||||
|
||||
return chartData;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
import BarChart from '../../charts/BarChart/BarChart';
|
||||
import ChartManager from '../../components/ChartManager/ChartManager';
|
||||
import { usePanelContextMenu } from '../../hooks/usePanelContextMenu';
|
||||
import { prepareBarPanelConfig, prepareBarPanelData } from './utils';
|
||||
|
||||
import '../Panel.styles.scss';
|
||||
|
||||
function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
const {
|
||||
panelMode,
|
||||
queryResponse,
|
||||
widget,
|
||||
onDragSelect,
|
||||
isFullViewMode,
|
||||
onToggleModelHandler,
|
||||
} = props;
|
||||
const uPlotRef = useRef<uPlot | null>(null);
|
||||
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
useEffect(() => {
|
||||
if (toScrollWidgetId === widget.id) {
|
||||
graphRef.current?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
graphRef.current?.focus();
|
||||
setToScrollWidgetId('');
|
||||
}
|
||||
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange(queryResponse);
|
||||
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [queryResponse]);
|
||||
|
||||
const {
|
||||
coordinates,
|
||||
popoverPosition,
|
||||
onClose,
|
||||
menuItemsConfig,
|
||||
clickHandlerWithContextMenu,
|
||||
} = usePanelContextMenu({
|
||||
widget,
|
||||
queryResponse,
|
||||
});
|
||||
|
||||
const config = useMemo(() => {
|
||||
return prepareBarPanelConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
currentQuery: widget.query,
|
||||
onClick: clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
apiResponse: queryResponse?.data?.payload as MetricRangePayloadProps,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale: minTimeScale,
|
||||
maxTimeScale: maxTimeScale,
|
||||
});
|
||||
}, [
|
||||
widget,
|
||||
isDarkMode,
|
||||
queryResponse?.data?.payload,
|
||||
clickHandlerWithContextMenu,
|
||||
onDragSelect,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
timezone,
|
||||
panelMode,
|
||||
]);
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
if (!queryResponse?.data?.payload) {
|
||||
return [];
|
||||
}
|
||||
return prepareBarPanelData(queryResponse?.data?.payload);
|
||||
}, [queryResponse?.data?.payload]);
|
||||
|
||||
const layoutChildren = useMemo(() => {
|
||||
if (!isFullViewMode) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ChartManager
|
||||
config={config}
|
||||
alignedData={chartData}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
onCancel={onToggleModelHandler}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
isFullViewMode,
|
||||
config,
|
||||
chartData,
|
||||
widget.yAxisUnit,
|
||||
onToggleModelHandler,
|
||||
]);
|
||||
|
||||
const onPlotDestroy = useCallback(() => {
|
||||
uPlotRef.current = null;
|
||||
}, []);
|
||||
|
||||
const onPlotRef = useCallback((plot: uPlot | null): void => {
|
||||
uPlotRef.current = plot;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="panel-container" ref={graphRef}>
|
||||
{containerDimensions.width > 0 && containerDimensions.height > 0 && (
|
||||
<BarChart
|
||||
config={config}
|
||||
legendConfig={{
|
||||
position: widget?.legendPosition ?? LegendPosition.BOTTOM,
|
||||
}}
|
||||
plotRef={onPlotRef}
|
||||
onDestroy={onPlotDestroy}
|
||||
yAxisUnit={widget.yAxisUnit}
|
||||
decimalPrecision={widget.decimalPrecision}
|
||||
timezone={timezone.value}
|
||||
data={chartData as uPlot.AlignedData}
|
||||
width={containerDimensions.width}
|
||||
height={containerDimensions.height}
|
||||
layoutChildren={layoutChildren}
|
||||
isStackedBarChart={widget.stackedBarChart ?? false}
|
||||
>
|
||||
<ContextMenu
|
||||
coordinates={coordinates}
|
||||
popoverPosition={popoverPosition}
|
||||
title={menuItemsConfig.header as string}
|
||||
items={menuItemsConfig.items}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</BarChart>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BarPanel;
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getInitialStackedBands } from 'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import {
|
||||
DrawStyle,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
VisibilityMode,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
import { AlignedData } from 'uplot';
|
||||
|
||||
import { PanelMode } from '../types';
|
||||
import { fillMissingXAxisTimestamps, getXAxisTimestamps } from '../utils';
|
||||
import { buildBaseConfig } from '../utils/baseConfigBuilder';
|
||||
|
||||
export function prepareBarPanelData(
|
||||
apiResponse: MetricRangePayloadProps,
|
||||
): AlignedData {
|
||||
const seriesList = apiResponse?.data?.result || [];
|
||||
const timestampArr = getXAxisTimestamps(seriesList);
|
||||
const yAxisValuesArr = fillMissingXAxisTimestamps(timestampArr, seriesList);
|
||||
return [timestampArr, ...yAxisValuesArr];
|
||||
}
|
||||
|
||||
export function prepareBarPanelConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
currentQuery,
|
||||
onClick,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
panelMode,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}: {
|
||||
widget: Widgets;
|
||||
isDarkMode: boolean;
|
||||
currentQuery: Query;
|
||||
onClick: OnClickPluginOpts['onClick'];
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
panelMode: PanelMode;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
}): UPlotConfigBuilder {
|
||||
const builder = buildBaseConfig({
|
||||
widget,
|
||||
isDarkMode,
|
||||
onClick,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
panelMode,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
});
|
||||
|
||||
builder.setCursor({
|
||||
focus: {
|
||||
prox: 1e3,
|
||||
},
|
||||
});
|
||||
|
||||
if (widget.stackedBarChart) {
|
||||
const seriesCount = (apiResponse?.data?.result?.length ?? 0) + 1; // +1 for 1-based uPlot series indices
|
||||
builder.setBands(getInitialStackedBands(seriesCount));
|
||||
}
|
||||
|
||||
const seriesList: QueryData[] = apiResponse?.data?.result || [];
|
||||
seriesList.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '', // query
|
||||
series.legend || '',
|
||||
);
|
||||
|
||||
const label = currentQuery
|
||||
? getLegend(series, currentQuery, baseLabelName)
|
||||
: baseLabelName;
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Bar,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
label: label,
|
||||
colorMapping: widget.customLegendColors ?? {},
|
||||
spanGaps: false,
|
||||
lineStyle: LineStyle.Solid,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
showPoints: VisibilityMode.Never,
|
||||
pointSize: 5,
|
||||
isDarkMode,
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
@@ -30,7 +30,6 @@ 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';
|
||||
|
||||
@@ -211,19 +210,13 @@ 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, dotMetricsEnabled],
|
||||
[globalSelectedInterval, topLevelOperations],
|
||||
);
|
||||
|
||||
const dataQueries = useGetQueriesRange(
|
||||
|
||||
@@ -23,8 +23,6 @@ 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';
|
||||
@@ -146,11 +144,6 @@ 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;
|
||||
@@ -221,7 +214,7 @@ function HostsList(): JSX.Element {
|
||||
</div>
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetHostsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetHostsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleQuickFiltersChange}
|
||||
/>
|
||||
|
||||
@@ -71,20 +71,12 @@ describe('InfraMonitoringHosts utils', () => {
|
||||
});
|
||||
|
||||
describe('GetHostsQuickFiltersConfig', () => {
|
||||
it('should return correct config when dotMetricsEnabled is true', () => {
|
||||
const result = GetHostsQuickFiltersConfig(true);
|
||||
it('should return correct config with dot-notation keys', () => {
|
||||
const result = GetHostsQuickFiltersConfig();
|
||||
|
||||
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,32 +211,18 @@ export const HostsQuickFiltersConfig: 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';
|
||||
|
||||
export function GetHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Host Name',
|
||||
attributeKey: {
|
||||
key: hostNameKey,
|
||||
key: 'host.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'system.cpu.load_average.15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -244,12 +230,12 @@ export function GetHostsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'OS Type',
|
||||
attributeKey: {
|
||||
key: osTypeKey,
|
||||
key: 'os.type',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'system.cpu.load_average.15m',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -257,7 +243,7 @@ export function GetHostsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -46,95 +46,34 @@ export const getClusterMetricsQueryPayload = (
|
||||
cluster: K8sClustersData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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');
|
||||
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';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -25,8 +25,6 @@ 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,11 +135,6 @@ 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'],
|
||||
@@ -231,8 +224,6 @@ function K8sClustersList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -241,10 +232,7 @@ function K8sClustersList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.CLUSTERS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.CLUSTERS),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -325,8 +313,6 @@ 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 dotToUnder: Record<string, keyof K8sClustersData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sClustersData['meta']> = {
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
'k8s.cluster.uid': 'k8s_cluster_uid',
|
||||
};
|
||||
@@ -151,7 +151,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof cluster.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof cluster.meta;
|
||||
const value = cluster.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -30,49 +30,28 @@ export const getDaemonSetMetricsQueryPayload = (
|
||||
daemonSet: K8sDaemonSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
|
||||
const k8sContainerCpuRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
|
||||
const k8sContainerMemoryRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
|
||||
const k8sContainerMemoryLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
|
||||
const k8sDaemonSetNameKey = dotMetricsEnabled
|
||||
? 'k8s.daemonset.name'
|
||||
: 'k8s_daemonset_name';
|
||||
const k8sDaemonSetNameKey = 'k8s.daemonset.name';
|
||||
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
|
||||
const k8sNamespaceNameKey = dotMetricsEnabled
|
||||
? 'k8s.namespace.name'
|
||||
: 'k8s_namespace_name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,8 +26,6 @@ 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,
|
||||
@@ -139,11 +137,6 @@ 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'],
|
||||
@@ -233,8 +226,6 @@ function K8sDaemonSetsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -243,10 +234,7 @@ function K8sDaemonSetsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DAEMONSETS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.DAEMONSETS),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -320,8 +308,6 @@ 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 dotToUnder: Record<string, keyof K8sDaemonSetsData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sDaemonSetsData['meta']> = {
|
||||
'k8s.daemonset.name': 'k8s_daemonset_name',
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
@@ -252,7 +252,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof daemonSet.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof daemonSet.meta;
|
||||
const value = daemonSet.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -30,45 +30,26 @@ export const getDeploymentMetricsQueryPayload = (
|
||||
deployment: K8sDeploymentsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const k8sPodCpuUtilizationKey = dotMetricsEnabled
|
||||
? 'k8s.pod.cpu.usage'
|
||||
: 'k8s_pod_cpu_usage';
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
|
||||
const k8sContainerCpuRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_request'
|
||||
: 'k8s_container_cpu_request';
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
|
||||
const k8sContainerCpuLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.cpu_limit'
|
||||
: 'k8s_container_cpu_limit';
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
|
||||
const k8sPodMemoryUsageKey = dotMetricsEnabled
|
||||
? 'k8s.pod.memory.usage'
|
||||
: 'k8s_pod_memory_usage';
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
|
||||
const k8sContainerMemoryRequestKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_request'
|
||||
: 'k8s_container_memory_request';
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
|
||||
const k8sContainerMemoryLimitKey = dotMetricsEnabled
|
||||
? 'k8s.container.memory_limit'
|
||||
: 'k8s_container_memory_limit';
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
|
||||
const k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
|
||||
const k8sDeploymentNameKey = dotMetricsEnabled
|
||||
? 'k8s.deployment.name'
|
||||
: 'k8s_deployment_name';
|
||||
const k8sDeploymentNameKey = 'k8s.deployment.name';
|
||||
|
||||
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,8 +26,6 @@ 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,
|
||||
@@ -140,11 +138,6 @@ 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'],
|
||||
@@ -234,8 +227,6 @@ function K8sDeploymentsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -246,7 +237,6 @@ function K8sDeploymentsList({
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.DEPLOYMENTS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
@@ -321,8 +311,6 @@ 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 dotToUnder: Record<string, keyof K8sDeploymentsData['meta']> = {
|
||||
const attributeToMetaKey: 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 = (dotToUnder[rawKey] ??
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof deployment.meta;
|
||||
const value = deployment.meta[metaKey];
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ 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';
|
||||
|
||||
@@ -54,7 +52,6 @@ interface EntityMetricsProps<T> {
|
||||
node: T,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
) => GetQueryResultsProps[];
|
||||
queryKey: string;
|
||||
category: K8sCategory;
|
||||
@@ -71,31 +68,14 @@ 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,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
[
|
||||
getEntityQueryPayload,
|
||||
entity,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
() => getEntityQueryPayload(entity, timeRange.startTime, timeRange.endTime),
|
||||
[getEntityQueryPayload, entity, timeRange.startTime, timeRange.endTime],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -97,12 +97,7 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
|
||||
plan_version: 'test-plan-version',
|
||||
},
|
||||
},
|
||||
featureFlags: [
|
||||
{
|
||||
name: 'DOT_METRICS_ENABLED',
|
||||
active: false,
|
||||
},
|
||||
],
|
||||
featureFlags: [],
|
||||
} as any);
|
||||
|
||||
const mockEntity = {
|
||||
@@ -385,7 +380,6 @@ describe('EntityMetrics', () => {
|
||||
mockEntity,
|
||||
mockTimeRange.startTime,
|
||||
mockTimeRange.endTime,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,8 +24,6 @@ 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,
|
||||
@@ -76,11 +74,6 @@ 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
|
||||
@@ -116,7 +109,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetPodsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetPodsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -136,7 +129,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNodesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetNodesQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -159,7 +152,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetNamespaceQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetNamespaceQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -179,7 +172,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetClustersQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetClustersQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -199,7 +192,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDeploymentsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetDeploymentsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -219,7 +212,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetJobsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetJobsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -239,7 +232,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetDaemonsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetDaemonsetsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -262,7 +255,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetStatefulsetsQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetStatefulsetsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
@@ -282,7 +275,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
children: (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={GetVolumesQuickFiltersConfig(dotMetricsEnabled)}
|
||||
config={GetVolumesQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
onFilterChange={handleFilterChange}
|
||||
/>
|
||||
|
||||
@@ -30,24 +30,13 @@ export const getJobMetricsQueryPayload = (
|
||||
job: K8sJobsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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';
|
||||
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';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -26,8 +26,6 @@ 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,
|
||||
@@ -134,11 +132,6 @@ 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'],
|
||||
@@ -215,15 +208,10 @@ function K8sJobsList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sJobsList(
|
||||
fetchGroupedByRowDataQuery as K8sJobsListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
} = useGetK8sJobsList(fetchGroupedByRowDataQuery as K8sJobsListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -231,10 +219,7 @@ function K8sJobsList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.JOBS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.JOBS),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -315,8 +300,6 @@ 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 dotToUnder: Record<string, keyof K8sJobsData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sJobsData['meta']> = {
|
||||
'k8s.job.name': 'k8s_job_name',
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
@@ -279,7 +279,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof job.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof job.meta;
|
||||
const value = job.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -25,8 +25,6 @@ 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,11 +136,6 @@ 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'],
|
||||
@@ -232,8 +225,6 @@ function K8sNamespacesList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -242,10 +233,7 @@ function K8sNamespacesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NAMESPACES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NAMESPACES),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -319,8 +307,6 @@ function K8sNamespacesList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const namespacesData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -54,95 +54,35 @@ export const getNamespaceMetricsQueryPayload = (
|
||||
namespace: K8sNamespacesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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',
|
||||
);
|
||||
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';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -122,7 +122,7 @@ export const getK8sNamespacesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sNamespacesRowData>[];
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sNamespacesData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sNamespacesData['meta']> = {
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
};
|
||||
@@ -137,7 +137,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof namespace.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof namespace.meta;
|
||||
const value = namespace.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -25,8 +25,6 @@ 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,11 +130,6 @@ 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'],
|
||||
@@ -220,15 +213,10 @@ function K8sNodesList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sNodesList(
|
||||
fetchGroupedByRowDataQuery as K8sNodesListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
} = useGetK8sNodesList(fetchGroupedByRowDataQuery as K8sNodesListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -236,10 +224,7 @@ function K8sNodesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NODES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NODES),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -320,8 +305,6 @@ function K8sNodesList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const nodesData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -54,88 +54,40 @@ export const getNodeMetricsQueryPayload = (
|
||||
node: K8sNodesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sNodeCpuUtilizationKey = getKey(
|
||||
'k8s.node.cpu.usage',
|
||||
'k8s_node_cpu_usage',
|
||||
);
|
||||
const k8sNodeCpuUtilizationKey = 'k8s.node.cpu.usage';
|
||||
|
||||
const k8sNodeAllocatableCpuKey = getKey(
|
||||
'k8s.node.allocatable_cpu',
|
||||
'k8s_node_allocatable_cpu',
|
||||
);
|
||||
const k8sNodeAllocatableCpuKey = 'k8s.node.allocatable_cpu';
|
||||
|
||||
const k8sContainerCpuRequestKey = getKey(
|
||||
'k8s.container.cpu_request',
|
||||
'k8s_container_cpu_request',
|
||||
);
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
|
||||
const k8sNodeMemoryUsageKey = getKey(
|
||||
'k8s.node.memory.usage',
|
||||
'k8s_node_memory_usage',
|
||||
);
|
||||
const k8sNodeMemoryUsageKey = 'k8s.node.memory.usage';
|
||||
|
||||
const k8sNodeAllocatableMemoryKey = getKey(
|
||||
'k8s.node.allocatable_memory',
|
||||
'k8s_node_allocatable_memory',
|
||||
);
|
||||
const k8sNodeAllocatableMemoryKey = 'k8s.node.allocatable_memory';
|
||||
|
||||
const k8sContainerMemoryRequestKey = getKey(
|
||||
'k8s.container.memory_request',
|
||||
'k8s_container_memory_request',
|
||||
);
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
|
||||
const k8sNodeMemoryWorkingSetKey = getKey(
|
||||
'k8s.node.memory.working_set',
|
||||
'k8s_node_memory_working_set',
|
||||
);
|
||||
const k8sNodeMemoryWorkingSetKey = 'k8s.node.memory.working_set';
|
||||
|
||||
const k8sNodeMemoryRssKey = getKey(
|
||||
'k8s.node.memory.rss',
|
||||
'k8s_node_memory_rss',
|
||||
);
|
||||
const k8sNodeMemoryRssKey = 'k8s.node.memory.rss';
|
||||
|
||||
const k8sPodCpuUtilizationKey = getKey(
|
||||
'k8s.pod.cpu.usage',
|
||||
'k8s_pod_cpu_usage',
|
||||
);
|
||||
const k8sPodCpuUtilizationKey = 'k8s.pod.cpu.usage';
|
||||
|
||||
const k8sPodMemoryUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
const k8sPodMemoryUsageKey = 'k8s.pod.memory.usage';
|
||||
|
||||
const k8sNodeNetworkErrorsKey = getKey(
|
||||
'k8s.node.network.errors',
|
||||
'k8s_node_network_errors',
|
||||
);
|
||||
const k8sNodeNetworkErrorsKey = 'k8s.node.network.errors';
|
||||
|
||||
const k8sNodeNetworkIoKey = getKey(
|
||||
'k8s.node.network.io',
|
||||
'k8s_node_network_io',
|
||||
);
|
||||
const k8sNodeNetworkIoKey = 'k8s.node.network.io';
|
||||
|
||||
const k8sNodeFilesystemUsageKey = getKey(
|
||||
'k8s.node.filesystem.usage',
|
||||
'k8s_node_filesystem_usage',
|
||||
);
|
||||
const k8sNodeFilesystemUsageKey = 'k8s.node.filesystem.usage';
|
||||
|
||||
const k8sNodeFilesystemCapacityKey = getKey(
|
||||
'k8s.node.filesystem.capacity',
|
||||
'k8s_node_filesystem_capacity',
|
||||
);
|
||||
const k8sNodeFilesystemCapacityKey = 'k8s.node.filesystem.capacity';
|
||||
|
||||
const k8sNodeFilesystemAvailableKey = getKey(
|
||||
'k8s.node.filesystem.available',
|
||||
'k8s_node_filesystem_available',
|
||||
);
|
||||
const k8sNodeFilesystemAvailableKey = 'k8s.node.filesystem.available';
|
||||
|
||||
const k8sNodeNameKey = getKey('k8s.node.name', 'k8s_node_name');
|
||||
const k8sNodeNameKey = 'k8s.node.name';
|
||||
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -152,7 +152,7 @@ export const getK8sNodesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sNodesRowData>[];
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sNodesData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sNodesData['meta']> = {
|
||||
'k8s.node.name': 'k8s_node_name',
|
||||
'k8s.cluster.name': 'k8s_cluster_name',
|
||||
'k8s.node.uid': 'k8s_node_uid',
|
||||
@@ -168,7 +168,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof node.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof node.meta;
|
||||
|
||||
const value = node.meta[metaKey];
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ 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,
|
||||
@@ -120,21 +118,13 @@ 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,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.PODS),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -244,8 +234,6 @@ function K8sPodsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const createFiltersForSelectedRowData = (
|
||||
@@ -323,15 +311,10 @@ function K8sPodsList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sPodsList(
|
||||
fetchGroupedByRowDataQuery as K8sPodsListPayload,
|
||||
{
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
} = useGetK8sPodsList(fetchGroupedByRowDataQuery as K8sPodsListPayload, {
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
|
||||
const podsData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
const totalCount = data?.payload?.data?.total || 0;
|
||||
|
||||
@@ -66,116 +66,56 @@ export const getPodMetricsQueryPayload = (
|
||||
pod: K8sPodsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
const getKey = (dotKey: string, underscoreKey: string): string =>
|
||||
dotMetricsEnabled ? dotKey : underscoreKey;
|
||||
const k8sContainerNameKey = getKey('k8s.container.name', 'k8s_container_name');
|
||||
const k8sContainerNameKey = 'k8s.container.name';
|
||||
|
||||
const k8sPodCpuUtilKey = getKey('k8s.pod.cpu.usage', 'k8s_pod_cpu_usage');
|
||||
const k8sPodCpuUtilKey = 'k8s.pod.cpu.usage';
|
||||
|
||||
const k8sPodCpuReqUtilKey = getKey(
|
||||
'k8s.pod.cpu_request_utilization',
|
||||
'k8s_pod_cpu_request_utilization',
|
||||
);
|
||||
const k8sPodCpuReqUtilKey = 'k8s.pod.cpu_request_utilization';
|
||||
|
||||
const k8sPodCpuLimitUtilKey = getKey(
|
||||
'k8s.pod.cpu_limit_utilization',
|
||||
'k8s_pod_cpu_limit_utilization',
|
||||
);
|
||||
const k8sPodCpuLimitUtilKey = 'k8s.pod.cpu_limit_utilization';
|
||||
|
||||
const k8sPodMemUsageKey = getKey(
|
||||
'k8s.pod.memory.usage',
|
||||
'k8s_pod_memory_usage',
|
||||
);
|
||||
const k8sPodMemUsageKey = 'k8s.pod.memory.usage';
|
||||
|
||||
const k8sPodMemReqUtilKey = getKey(
|
||||
'k8s.pod.memory_request_utilization',
|
||||
'k8s_pod_memory_request_utilization',
|
||||
);
|
||||
const k8sPodMemReqUtilKey = 'k8s.pod.memory_request_utilization';
|
||||
|
||||
const k8sPodMemLimitUtilKey = getKey(
|
||||
'k8s.pod.memory_limit_utilization',
|
||||
'k8s_pod_memory_limit_utilization',
|
||||
);
|
||||
const k8sPodMemLimitUtilKey = 'k8s.pod.memory_limit_utilization';
|
||||
|
||||
const k8sPodMemRssKey = getKey('k8s.pod.memory.rss', 'k8s_pod_memory_rss');
|
||||
const k8sPodMemRssKey = 'k8s.pod.memory.rss';
|
||||
|
||||
const k8sPodMemWorkingSetKey = getKey(
|
||||
'k8s.pod.memory.working_set',
|
||||
'k8s_pod_memory_working_set',
|
||||
);
|
||||
const k8sPodMemWorkingSetKey = 'k8s.pod.memory.working_set';
|
||||
|
||||
const k8sPodMemMajorPFKey = getKey(
|
||||
'k8s.pod.memory.major_page_faults',
|
||||
'k8s_pod_memory_major_page_faults',
|
||||
);
|
||||
const k8sPodMemMajorPFKey = 'k8s.pod.memory.major_page_faults';
|
||||
|
||||
const containerCpuUtilKey = getKey(
|
||||
'container.cpu.usage',
|
||||
'container_cpu_usage',
|
||||
);
|
||||
const containerCpuUtilKey = 'container.cpu.usage';
|
||||
|
||||
const k8sContainerCpuRequestKey = getKey(
|
||||
'k8s.container.cpu_request',
|
||||
'k8s_container_cpu_request',
|
||||
);
|
||||
const k8sContainerCpuRequestKey = 'k8s.container.cpu_request';
|
||||
|
||||
const k8sContainerCpuLimitKey = getKey(
|
||||
'k8s.container.cpu_limit',
|
||||
'k8s_container_cpu_limit',
|
||||
);
|
||||
const k8sContainerCpuLimitKey = 'k8s.container.cpu_limit';
|
||||
|
||||
const k8sContainerMemoryLimitKey = getKey(
|
||||
'k8s.container.memory_limit',
|
||||
'k8s_container_memory_limit',
|
||||
);
|
||||
const k8sContainerMemoryLimitKey = 'k8s.container.memory_limit';
|
||||
|
||||
const k8sContainerMemoryRequestKey = getKey(
|
||||
'k8s.container.memory_request',
|
||||
'k8s_container_memory_request',
|
||||
);
|
||||
const k8sContainerMemoryRequestKey = 'k8s.container.memory_request';
|
||||
|
||||
const containerMemUsageKey = getKey(
|
||||
'container.memory.usage',
|
||||
'container_memory_usage',
|
||||
);
|
||||
const containerMemUsageKey = 'container.memory.usage';
|
||||
|
||||
const containerMemWorkingSetKey = getKey(
|
||||
'container.memory.working_set',
|
||||
'container_memory_working_set',
|
||||
);
|
||||
const containerMemWorkingSetKey = 'container.memory.working_set';
|
||||
|
||||
const containerMemRssKey = getKey(
|
||||
'container.memory.rss',
|
||||
'container_memory_rss',
|
||||
);
|
||||
const containerMemRssKey = 'container.memory.rss';
|
||||
|
||||
const k8sPodNetworkIoKey = getKey('k8s.pod.network.io', 'k8s_pod_network_io');
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
|
||||
const k8sPodNetworkErrorsKey = getKey(
|
||||
'k8s.pod.network.errors',
|
||||
'k8s_pod_network_errors',
|
||||
);
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
|
||||
const k8sPodFilesystemCapacityKey = getKey(
|
||||
'k8s.pod.filesystem.capacity',
|
||||
'k8s_pod_filesystem_capacity',
|
||||
);
|
||||
const k8sPodFilesystemCapacityKey = 'k8s.pod.filesystem.capacity';
|
||||
|
||||
const k8sPodFilesystemAvailableKey = getKey(
|
||||
'k8s.pod.filesystem.available',
|
||||
'k8s_pod_filesystem_available',
|
||||
);
|
||||
const k8sPodFilesystemAvailableKey = 'k8s.pod.filesystem.available';
|
||||
|
||||
const k8sPodFilesystemUsageKey = getKey(
|
||||
'k8s.pod.filesystem.usage',
|
||||
'k8s_pod_filesystem_usage',
|
||||
);
|
||||
const k8sPodFilesystemUsageKey = 'k8s.pod.filesystem.usage';
|
||||
|
||||
const k8sPodNameKey = getKey('k8s.pod.name', 'k8s_pod_name');
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
|
||||
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
return [
|
||||
{
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
|
||||
@@ -26,8 +26,6 @@ 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,
|
||||
@@ -139,11 +137,6 @@ 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'],
|
||||
@@ -233,8 +226,6 @@ function K8sStatefulSetsList({
|
||||
queryKey: groupedByRowDataQueryKey,
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -245,7 +236,6 @@ function K8sStatefulSetsList({
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.STATEFULSETS,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
@@ -327,8 +317,6 @@ function K8sStatefulSetsList({
|
||||
enabled: !!query,
|
||||
keepPreviousData: true,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const statefulSetsData = useMemo(() => data?.payload?.data?.records || [], [
|
||||
|
||||
@@ -38,54 +38,25 @@ export const getStatefulSetMetricsQueryPayload = (
|
||||
statefulSet: K8sStatefulSetsData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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 k8sStatefulSetNameKey = 'k8s.statefulset.name';
|
||||
const k8sNamespaceNameKey = 'k8s.namespace.name';
|
||||
const k8sPodNameKey = 'k8s.pod.name';
|
||||
|
||||
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 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 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 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 k8sPodNetworkIoKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.io'
|
||||
: 'k8s_pod_network_io';
|
||||
const k8sPodNetworkErrorsKey = dotMetricsEnabled
|
||||
? 'k8s.pod.network.errors'
|
||||
: 'k8s_pod_network_errors';
|
||||
const k8sPodNetworkIoKey = 'k8s.pod.network.io';
|
||||
const k8sPodNetworkErrorsKey = 'k8s.pod.network.errors';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -236,7 +236,7 @@ export const getK8sStatefulSetsListColumns = (
|
||||
return columnsConfig as ColumnType<K8sStatefulSetsRowData>[];
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sStatefulSetsData['meta']> = {
|
||||
const attributeToMetaKey: 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 = (dotToUnder[rawKey] ??
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof statefulSet.meta;
|
||||
const value = statefulSet.meta[metaKey];
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ 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,
|
||||
@@ -139,11 +137,6 @@ 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'],
|
||||
@@ -201,15 +194,10 @@ function K8sVolumesList({
|
||||
isLoading: isLoadingGroupedByRowData,
|
||||
isError: isErrorGroupedByRowData,
|
||||
refetch: fetchGroupedByRowData,
|
||||
} = useGetK8sVolumesList(
|
||||
fetchGroupedByRowDataQuery as K8sVolumesListPayload,
|
||||
{
|
||||
queryKey: ['volumeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
} = useGetK8sVolumesList(fetchGroupedByRowDataQuery as K8sVolumesListPayload, {
|
||||
queryKey: ['volumeList', fetchGroupedByRowDataQuery],
|
||||
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
|
||||
});
|
||||
|
||||
const {
|
||||
data: groupByFiltersData,
|
||||
@@ -217,10 +205,7 @@ function K8sVolumesList({
|
||||
} = useGetAggregateKeys(
|
||||
{
|
||||
dataSource: currentQuery.builder.queryData[0].dataSource,
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(
|
||||
K8sCategory.NODES,
|
||||
dotMetricsEnabled,
|
||||
),
|
||||
aggregateAttribute: GetK8sEntityToAggregateAttribute(K8sCategory.NODES),
|
||||
aggregateOperator: 'noop',
|
||||
searchText: '',
|
||||
tagType: '',
|
||||
@@ -268,8 +253,6 @@ function K8sVolumesList({
|
||||
queryKey: ['volumeList', query],
|
||||
enabled: !!query,
|
||||
},
|
||||
undefined,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
|
||||
const volumesData = useMemo(() => data?.payload?.data?.records || [], [data]);
|
||||
|
||||
@@ -34,38 +34,17 @@ export const getVolumeQueryPayload = (
|
||||
volume: K8sVolumesData,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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}}';
|
||||
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}}';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -142,7 +142,7 @@ export const getK8sVolumesListColumns = (
|
||||
return columnsConfig as ColumnType<K8sVolumesRowData>[];
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sVolumesData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sVolumesData['meta']> = {
|
||||
'k8s.namespace.name': 'k8s_namespace_name',
|
||||
'k8s.node.name': 'k8s_node_name',
|
||||
'k8s.pod.name': 'k8s_pod_name',
|
||||
@@ -161,7 +161,8 @@ const getGroupByEle = (
|
||||
groupBy.forEach((group) => {
|
||||
const rawKey = group.key as string;
|
||||
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof volume.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof volume.meta;
|
||||
|
||||
const value = volume.meta[metaKey];
|
||||
|
||||
|
||||
@@ -36,21 +36,7 @@ export const K8sCategories = {
|
||||
VOLUMES: 'volumes',
|
||||
};
|
||||
|
||||
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 = {
|
||||
export const K8sEntityToAggregateAttributeMap = {
|
||||
[K8sCategory.HOSTS]: 'system.cpu.load_average.15m',
|
||||
[K8sCategory.PODS]: 'k8s.pod.cpu.usage',
|
||||
[K8sCategory.NODES]: 'k8s.node.cpu.usage',
|
||||
@@ -66,51 +52,23 @@ export const dotMap = {
|
||||
|
||||
export function GetK8sEntityToAggregateAttribute(
|
||||
category: K8sCategory,
|
||||
dotMetricsEnabled: boolean,
|
||||
): string {
|
||||
return dotMetricsEnabled ? dotMap[category] : underscoreMap[category];
|
||||
return K8sEntityToAggregateAttributeMap[category];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Pod',
|
||||
attributeKey: {
|
||||
key: podKey,
|
||||
key: 'k8s.pod.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
id: `${podKey}--string--tag--true`,
|
||||
id: 'k8s.pod.name--string--tag--true',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -118,13 +76,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${namespaceKey}--string--resource--false`,
|
||||
id: 'k8s.namespace.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -132,13 +90,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Node',
|
||||
attributeKey: {
|
||||
key: nodeKey,
|
||||
key: 'k8s.node.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${nodeKey}--string--resource--false`,
|
||||
id: 'k8s.node.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -146,13 +104,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource--false`,
|
||||
id: 'k8s.cluster.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -160,13 +118,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Deployment',
|
||||
attributeKey: {
|
||||
key: deploymentKey,
|
||||
key: 'k8s.deployment.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${deploymentKey}--string--resource--false`,
|
||||
id: 'k8s.deployment.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -174,13 +132,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Statefulset',
|
||||
attributeKey: {
|
||||
key: statefulsetKey,
|
||||
key: 'k8s.statefulset.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${statefulsetKey}--string--resource--false`,
|
||||
id: 'k8s.statefulset.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -188,13 +146,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'DaemonSet',
|
||||
attributeKey: {
|
||||
key: daemonsetKey,
|
||||
key: 'k8s.daemonset.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${daemonsetKey}--string--resource--false`,
|
||||
id: 'k8s.daemonset.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -202,13 +160,13 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Job',
|
||||
attributeKey: {
|
||||
key: jobKey,
|
||||
key: 'k8s.job.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${jobKey}--string--resource--false`,
|
||||
id: 'k8s.job.name--string--resource--false',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilizationMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
@@ -216,7 +174,7 @@ export function GetPodsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -225,33 +183,19 @@ export function GetPodsQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Node Name',
|
||||
attributeKey: {
|
||||
key: nodeKey,
|
||||
key: 'k8s.node.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${nodeKey}--string--resource--true`,
|
||||
id: 'k8s.node.name--string--resource--true',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -259,13 +203,13 @@ export function GetNodesQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource--true`,
|
||||
id: 'k8s.cluster.name--string--resource--true',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -273,7 +217,7 @@ export function GetNodesQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -282,32 +226,19 @@ export function GetNodesQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -315,13 +246,13 @@ export function GetNamespaceQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -329,7 +260,7 @@ export function GetNamespaceQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -338,29 +269,19 @@ export function GetNamespaceQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetClustersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: cpuUtilMetric,
|
||||
aggregateAttribute: 'k8s.node.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -368,7 +289,7 @@ export function GetClustersQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -377,25 +298,16 @@ export function GetClustersQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
export function GetContainersQuickFiltersConfig(
|
||||
dotMetricsEnabled: boolean,
|
||||
): IQuickFiltersConfig[] {
|
||||
const containerKey = dotMetricsEnabled
|
||||
? 'k8s.container.name'
|
||||
: 'k8s_container_name';
|
||||
const environmentKey = dotMetricsEnabled
|
||||
? 'deployment.environment'
|
||||
: 'deployment_environment';
|
||||
|
||||
export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Container',
|
||||
attributeKey: {
|
||||
key: containerKey,
|
||||
key: 'k8s.container.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${containerKey}--string--resource`,
|
||||
id: 'k8s.container.name--string--resource',
|
||||
},
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -403,7 +315,7 @@ export function GetContainersQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -412,35 +324,19 @@ export function GetContainersQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'PVC Volume Claim Name',
|
||||
attributeKey: {
|
||||
key: pvcKey,
|
||||
key: 'k8s.persistentvolumeclaim.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${pvcKey}--string--resource`,
|
||||
id: 'k8s.persistentvolumeclaim.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: volumeMetric,
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -448,13 +344,13 @@ export function GetVolumesQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: volumeMetric,
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -462,13 +358,13 @@ export function GetVolumesQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: volumeMetric,
|
||||
aggregateAttribute: 'k8s.volume.capacity',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -476,7 +372,7 @@ export function GetVolumesQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -485,33 +381,19 @@ export function GetVolumesQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Deployment Name',
|
||||
attributeKey: {
|
||||
key: deployKey,
|
||||
key: 'k8s.deployment.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${deployKey}--string--resource`,
|
||||
id: 'k8s.deployment.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -519,13 +401,13 @@ export function GetDeploymentsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -533,13 +415,13 @@ export function GetDeploymentsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -547,7 +429,7 @@ export function GetDeploymentsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -556,33 +438,19 @@ export function GetDeploymentsQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Statefulset Name',
|
||||
attributeKey: {
|
||||
key: ssKey,
|
||||
key: 'k8s.statefulset.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${ssKey}--string--resource`,
|
||||
id: 'k8s.statefulset.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -590,13 +458,13 @@ export function GetStatefulsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${namespaceKey}--string--resource`,
|
||||
id: 'k8s.namespace.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -604,13 +472,13 @@ export function GetStatefulsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${clusterKey}--string--resource`,
|
||||
id: 'k8s.cluster.name--string--resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metric,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -618,7 +486,7 @@ export function GetStatefulsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -627,35 +495,19 @@ export function GetStatefulsetsQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'DaemonSet Name',
|
||||
attributeKey: {
|
||||
key: nameKey,
|
||||
key: 'k8s.daemonset.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${nameKey}--string--resource--true`,
|
||||
id: 'k8s.daemonset.name--string--resource--true',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -663,12 +515,12 @@ export function GetDaemonsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -676,12 +528,12 @@ export function GetDaemonsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -689,7 +541,7 @@ export function GetDaemonsetsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
@@ -698,33 +550,19 @@ export function GetDaemonsetsQuickFiltersConfig(
|
||||
];
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
return [
|
||||
{
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Job Name',
|
||||
attributeKey: {
|
||||
key: nameKey,
|
||||
key: 'k8s.job.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
id: `${nameKey}--string--resource--true`,
|
||||
id: 'k8s.job.name--string--resource--true',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -732,12 +570,12 @@ export function GetJobsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Namespace Name',
|
||||
attributeKey: {
|
||||
key: namespaceKey,
|
||||
key: 'k8s.namespace.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -745,12 +583,12 @@ export function GetJobsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Cluster Name',
|
||||
attributeKey: {
|
||||
key: clusterKey,
|
||||
key: 'k8s.cluster.name',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: metricName,
|
||||
aggregateAttribute: 'k8s.pod.cpu.usage',
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
@@ -758,7 +596,7 @@ export function GetJobsQuickFiltersConfig(
|
||||
type: FiltersType.CHECKBOX,
|
||||
title: 'Environment',
|
||||
attributeKey: {
|
||||
key: environmentKey,
|
||||
key: 'deployment.environment',
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
|
||||
@@ -299,7 +299,7 @@ export const getK8sPodsListColumns = (
|
||||
return updatedColumnsConfig as ColumnType<K8sPodsRowData>[];
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sPodsData['meta']> = {
|
||||
const attributeToMetaKey: Record<string, keyof K8sPodsData['meta']> = {
|
||||
'k8s.cronjob.name': 'k8s_cronjob_name',
|
||||
'k8s.daemonset.name': 'k8s_daemonset_name',
|
||||
'k8s.deployment.name': 'k8s_deployment_name',
|
||||
@@ -322,7 +322,8 @@ const getGroupByEle = (
|
||||
const rawKey = group.key as string;
|
||||
|
||||
// Choose mapped key if present, otherwise use rawKey
|
||||
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof pod.meta;
|
||||
const metaKey = (attributeToMetaKey[rawKey] ??
|
||||
rawKey) as keyof typeof pod.meta;
|
||||
const value = pod.meta[metaKey];
|
||||
|
||||
groupByValues.push(value);
|
||||
|
||||
@@ -16,8 +16,6 @@ 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,
|
||||
@@ -52,23 +50,12 @@ 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,
|
||||
dotMetricsEnabled,
|
||||
);
|
||||
return getNodeQueryPayload(clusterName, nodeName, start, end);
|
||||
}
|
||||
return getHostQueryPayload(hostName, start, end, dotMetricsEnabled);
|
||||
}, [nodeName, hostName, clusterName, start, end, dotMetricsEnabled]);
|
||||
return getHostQueryPayload(hostName, start, end);
|
||||
}, [nodeName, hostName, clusterName, start, end]);
|
||||
|
||||
const widgetInfo = nodeName ? nodeWidgetInfo : hostWidgetInfo;
|
||||
const queries = useQueries(
|
||||
|
||||
@@ -11,13 +11,11 @@ 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({
|
||||
@@ -53,14 +51,9 @@ 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, dotMetricsEnabled),
|
||||
[clusterName, end, podName, start, dotMetricsEnabled],
|
||||
() => getPodQueryPayload(clusterName, podName, start, end),
|
||||
[clusterName, end, podName, start],
|
||||
);
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload) => ({
|
||||
|
||||
@@ -10,48 +10,21 @@ export const getPodQueryPayload = (
|
||||
podName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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}}';
|
||||
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}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1028,36 +1001,17 @@ export const getNodeQueryPayload = (
|
||||
nodeName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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}}';
|
||||
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}}';
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -1587,42 +1541,21 @@ export const getHostQueryPayload = (
|
||||
hostName: string,
|
||||
start: number,
|
||||
end: number,
|
||||
dotMetricsEnabled: boolean,
|
||||
): GetQueryResultsProps[] => {
|
||||
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';
|
||||
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';
|
||||
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -21,7 +21,6 @@ export const databaseCallsRPS = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallsRPSProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -33,7 +32,7 @@ export const databaseCallsRPS = ({
|
||||
const groupBy: BaseAutocompleteData[] = [
|
||||
{
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled ? WidgetKeys.Db_system : WidgetKeys.Db_system_norm,
|
||||
key: WidgetKeys.DbSystem,
|
||||
type: 'tag',
|
||||
},
|
||||
];
|
||||
@@ -42,9 +41,7 @@ export const databaseCallsRPS = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -75,7 +72,6 @@ export const databaseCallsRPS = ({
|
||||
export const databaseCallsAvgDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: DatabaseCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozDbLatencySum,
|
||||
@@ -92,9 +88,7 @@ export const databaseCallsAvgDuration = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@ export const externalCallErrorPercent = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||
@@ -49,9 +48,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -61,7 +58,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -74,9 +71,7 @@ export const externalCallErrorPercent = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -120,7 +115,6 @@ export const externalCallErrorPercent = ({
|
||||
export const externalCallDuration = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -141,9 +135,7 @@ export const externalCallDuration = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -183,7 +175,6 @@ export const externalCallRpsByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
@@ -198,9 +189,7 @@ export const externalCallRpsByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -231,7 +220,6 @@ export const externalCallDurationByAddress = ({
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}: ExternalCallDurationByAddressProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
dataType: DataTypes.Float64,
|
||||
@@ -251,9 +239,7 @@ export const externalCallDurationByAddress = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -37,15 +37,10 @@ export const latency = ({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable = false,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}: LatencyProps): QueryBuilderData => {
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
const signozLatencyBucketMetrics = WidgetKeys.SignozLatencyBucket;
|
||||
|
||||
const signozMetricsServiceName = dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm;
|
||||
const signozMetricsServiceName = WidgetKeys.OTelServiceName;
|
||||
const newAutoCompleteData: BaseAutocompleteData = {
|
||||
key: isSpanMetricEnable
|
||||
? signozLatencyBucketMetrics
|
||||
@@ -287,28 +282,21 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
threashold,
|
||||
delta,
|
||||
metricsBuckets,
|
||||
dotMetricsEnabled,
|
||||
}: ApDexMetricsQueryBuilderQueriesProps): QueryBuilderData => {
|
||||
const autoCompleteDataA: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataB: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
|
||||
const autoCompleteDataC: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -317,9 +305,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -343,7 +329,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -363,9 +349,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -399,7 +383,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -409,9 +393,7 @@ export const apDexMetricsQueryBuilderQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -474,13 +456,10 @@ export const operationPerSec = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteData: BaseAutocompleteData[] = [
|
||||
{
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
},
|
||||
@@ -491,9 +470,7 @@ export const operationPerSec = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -534,7 +511,6 @@ export const errorPercentage = ({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations,
|
||||
dotMetricsEnabled,
|
||||
}: OperationPerSecProps): QueryBuilderData => {
|
||||
const autocompleteDataA: BaseAutocompleteData = {
|
||||
key: WidgetKeys.SignozCallsTotal,
|
||||
@@ -553,9 +529,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -575,7 +549,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
dataType: DataTypes.Int64,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
@@ -589,9 +563,7 @@ export const errorPercentage = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
|
||||
@@ -21,12 +21,9 @@ import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
|
||||
|
||||
export const topOperationQueries = ({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}: TopOperationQueryFactoryProps): QueryBuilderData => {
|
||||
const latencyAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm,
|
||||
key: WidgetKeys.SignozLatencyBucket,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -38,9 +35,7 @@ export const topOperationQueries = ({
|
||||
};
|
||||
|
||||
const numOfCallAutoCompleteData: BaseAutocompleteData = {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.SignozLatencyCount
|
||||
: WidgetKeys.SignozLatencyCountNorm,
|
||||
key: WidgetKeys.SignozLatencyCount,
|
||||
dataType: DataTypes.Float64,
|
||||
type: '',
|
||||
};
|
||||
@@ -49,9 +44,7 @@ export const topOperationQueries = ({
|
||||
{
|
||||
id: '',
|
||||
key: {
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
dataType: DataTypes.String,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
@@ -65,9 +58,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.String,
|
||||
key: dotMetricsEnabled
|
||||
? WidgetKeys.Service_name
|
||||
: WidgetKeys.Service_name_norm,
|
||||
key: WidgetKeys.OTelServiceName,
|
||||
type: MetricsType.Resource,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
@@ -77,7 +68,7 @@ export const topOperationQueries = ({
|
||||
id: '',
|
||||
key: {
|
||||
dataType: DataTypes.Int64,
|
||||
key: dotMetricsEnabled ? WidgetKeys.StatusCode : WidgetKeys.StatusCodeNorm,
|
||||
key: WidgetKeys.StatusCode,
|
||||
type: MetricsType.Tag,
|
||||
},
|
||||
op: OPERATORS.IN,
|
||||
|
||||
@@ -27,8 +27,6 @@ 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';
|
||||
@@ -83,12 +81,7 @@ function DBCall(): JSX.Element {
|
||||
[queries],
|
||||
);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const legend = dotMetricsEnabled ? '{{db.system}}' : '{{db_system}}';
|
||||
const legend = '{{db.system}}';
|
||||
|
||||
const databaseCallsRPSWidget = useMemo(
|
||||
() =>
|
||||
@@ -100,7 +93,6 @@ function DBCall(): JSX.Element {
|
||||
servicename,
|
||||
legend,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -111,7 +103,7 @@ function DBCall(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.dbCallsRPS,
|
||||
fillSpans: false,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled, legend],
|
||||
[servicename, tagFilterItems, legend],
|
||||
);
|
||||
const databaseCallsAverageDurationWidget = useMemo(
|
||||
() =>
|
||||
@@ -122,7 +114,6 @@ function DBCall(): JSX.Element {
|
||||
builder: databaseCallsAvgDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -133,7 +124,7 @@ function DBCall(): JSX.Element {
|
||||
id: GraphTitle.DATABASE_CALLS_AVG_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
@@ -151,7 +142,7 @@ function DBCall(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
|
||||
@@ -29,8 +29,6 @@ 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';
|
||||
@@ -78,11 +76,6 @@ 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({
|
||||
@@ -93,7 +86,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -103,7 +95,7 @@ function External(): JSX.Element {
|
||||
yAxisUnit: '%',
|
||||
id: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const selectedTraceTags = useMemo(
|
||||
@@ -120,7 +112,6 @@ function External(): JSX.Element {
|
||||
builder: externalCallDuration({
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -131,7 +122,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const errorApmToTraceQuery = useGetAPMToTracesQueries({
|
||||
@@ -165,7 +156,7 @@ function External(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current) {
|
||||
const selectedEnvironments = queries.find(
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -188,7 +179,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -199,7 +189,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const externalCallDurationAddressWidget = useMemo(
|
||||
@@ -212,7 +202,6 @@ function External(): JSX.Element {
|
||||
servicename,
|
||||
legend: legend.address,
|
||||
tagFilterItems,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -223,7 +212,7 @@ function External(): JSX.Element {
|
||||
id: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems],
|
||||
);
|
||||
|
||||
const apmToTraceQuery = useGetAPMToTracesQueries({
|
||||
|
||||
@@ -93,15 +93,11 @@ 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(dotMetricsEnabled),
|
||||
(val) => val.tagKey === getResourceDeploymentKeys(),
|
||||
)?.tagValue;
|
||||
|
||||
logEvent('APM: Service detail page visited', {
|
||||
@@ -159,7 +155,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -169,7 +164,7 @@ function Application(): JSX.Element {
|
||||
yAxisUnit: 'ops',
|
||||
id: SERVICE_CHART_ID.rps,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const errorPercentageWidget = useMemo(
|
||||
@@ -182,7 +177,6 @@ function Application(): JSX.Element {
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperations: topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -193,7 +187,7 @@ function Application(): JSX.Element {
|
||||
id: SERVICE_CHART_ID.errorPercentage,
|
||||
fillSpans: true,
|
||||
}),
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute, dotMetricsEnabled],
|
||||
[servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const stepInterval = useMemo(
|
||||
|
||||
@@ -20,8 +20,6 @@ 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';
|
||||
|
||||
@@ -36,10 +34,6 @@ 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({
|
||||
@@ -53,7 +47,6 @@ function ApDexMetrics({
|
||||
threashold: thresholdValue || 0,
|
||||
delta: delta || false,
|
||||
metricsBuckets: metricsBuckets || [],
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -79,7 +72,6 @@ function ApDexMetrics({
|
||||
tagFilterItems,
|
||||
thresholdValue,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ 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';
|
||||
@@ -20,17 +18,8 @@ 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(
|
||||
signozLatencyBucketMetrics,
|
||||
WidgetKeys.SignozLatencyBucket,
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
@@ -56,10 +56,6 @@ function ServiceOverview({
|
||||
[isSpanMetricEnable, queries],
|
||||
);
|
||||
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const latencyWidget = useMemo(
|
||||
() =>
|
||||
getWidgetQueryBuilder({
|
||||
@@ -71,7 +67,6 @@ function ServiceOverview({
|
||||
tagFilterItems,
|
||||
isSpanMetricEnable,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
@@ -81,13 +76,7 @@ function ServiceOverview({
|
||||
yAxisUnit: 'ns',
|
||||
id: SERVICE_CHART_ID.latency,
|
||||
}),
|
||||
[
|
||||
isSpanMetricEnable,
|
||||
servicename,
|
||||
tagFilterItems,
|
||||
topLevelOperationsRoute,
|
||||
dotMetricsEnabled,
|
||||
],
|
||||
[isSpanMetricEnable, servicename, tagFilterItems, topLevelOperationsRoute],
|
||||
);
|
||||
|
||||
const isQueryEnabled =
|
||||
|
||||
@@ -18,8 +18,6 @@ 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';
|
||||
@@ -42,11 +40,6 @@ 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({
|
||||
@@ -55,14 +48,13 @@ function TopOperationMetrics(): JSX.Element {
|
||||
promql: [],
|
||||
builder: topOperationQueries({
|
||||
servicename,
|
||||
dotMetricsEnabled,
|
||||
}),
|
||||
clickhouse_sql: [],
|
||||
id: uuid(),
|
||||
},
|
||||
panelTypes: PANEL_TYPES.TABLE,
|
||||
}),
|
||||
[servicename, dotMetricsEnabled],
|
||||
[servicename],
|
||||
);
|
||||
|
||||
const updatedQuery = updateStepInterval(keyOperationWidget.query);
|
||||
|
||||
@@ -10,7 +10,6 @@ export interface IServiceName {
|
||||
|
||||
export interface TopOperationQueryFactoryProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
@@ -20,7 +19,6 @@ export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
|
||||
export interface ExternalCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface BuilderQueriesProps {
|
||||
@@ -52,7 +50,6 @@ export interface OperationPerSecProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
topLevelOperations: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface LatencyProps {
|
||||
@@ -60,7 +57,6 @@ export interface LatencyProps {
|
||||
tagFilterItems: TagFilterItem[];
|
||||
isSpanMetricEnable?: boolean;
|
||||
topLevelOperationsRoute: string[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface ApDexProps {
|
||||
@@ -78,5 +74,4 @@ export interface TableRendererProps {
|
||||
export interface ApDexMetricsQueryBuilderQueriesProps extends ApDexProps {
|
||||
delta: boolean;
|
||||
metricsBuckets: number[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -85,14 +85,11 @@ export enum WidgetKeys {
|
||||
HasError = 'hasError',
|
||||
Address = 'address',
|
||||
DurationNano = 'durationNano',
|
||||
StatusCodeNorm = 'status_code',
|
||||
StatusCode = 'status.code',
|
||||
Operation = 'operation',
|
||||
OperationName = 'operationName',
|
||||
Service_name_norm = 'service_name',
|
||||
Service_name = 'service.name',
|
||||
OTelServiceName = 'service.name',
|
||||
ServiceName = 'serviceName',
|
||||
SignozLatencyCountNorm = 'signoz_latency_count',
|
||||
SignozLatencyCount = 'signoz_latency.count',
|
||||
SignozDBLatencyCount = 'signoz_db_latency_count',
|
||||
DatabaseCallCount = 'signoz_database_call_count',
|
||||
@@ -101,10 +98,8 @@ export enum WidgetKeys {
|
||||
SignozCallsTotal = 'signoz_calls_total',
|
||||
SignozExternalCallLatencyCount = 'signoz_external_call_latency_count',
|
||||
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
|
||||
Signoz_latency_bucket_norm = 'signoz_latency_bucket',
|
||||
Signoz_latency_bucket = 'signoz_latency.bucket',
|
||||
Db_system = 'db.system',
|
||||
Db_system_norm = 'db_system',
|
||||
SignozLatencyBucket = 'signoz_latency.bucket',
|
||||
DbSystem = 'db.system',
|
||||
}
|
||||
|
||||
export const topOperationMetricsDownloadOptions: DownloadOptions = {
|
||||
|
||||
@@ -32,5 +32,4 @@ export interface DatabaseCallsRPSProps extends DatabaseCallProps {
|
||||
export interface DatabaseCallProps {
|
||||
servicename: IServiceName['servicename'];
|
||||
tagFilterItems: TagFilterItem[];
|
||||
dotMetricsEnabled: boolean;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.auth-domain-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,29 +15,5 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 24px;
|
||||
|
||||
.auth-domain-list-action-link {
|
||||
cursor: pointer;
|
||||
color: var(--bg-robin-500);
|
||||
transition: color 0.3s;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
color: var(--bg-cherry-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.auth-domain-list-na {
|
||||
padding-left: 6px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Button, Form, Modal } from 'antd';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import {
|
||||
useCreateAuthDomain,
|
||||
useUpdateAuthDomain,
|
||||
} from 'api/generated/services/authdomains';
|
||||
import {
|
||||
AuthtypesGettableAuthDomainDTO,
|
||||
AuthtypesGoogleConfigDTO,
|
||||
AuthtypesOIDCConfigDTO,
|
||||
AuthtypesPostableAuthDomainDTO,
|
||||
AuthtypesRoleMappingDTO,
|
||||
AuthtypesSamlConfigDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import put from 'api/v1/domains/id/put';
|
||||
import post from 'api/v1/domains/post';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { ErrorV2Resp } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { GettableAuthDomain } from 'types/api/v1/domains/list';
|
||||
import { PostableAuthDomain } from 'types/api/v1/domains/post';
|
||||
|
||||
import AuthnProviderSelector from './AuthnProviderSelector';
|
||||
import ConfigureGoogleAuthAuthnProvider from './Providers/AuthnGoogleAuth';
|
||||
@@ -33,22 +20,7 @@ import './CreateEdit.styles.scss';
|
||||
interface CreateOrEditProps {
|
||||
isCreate: boolean;
|
||||
onClose: () => void;
|
||||
record?: AuthtypesGettableAuthDomainDTO;
|
||||
}
|
||||
|
||||
// Form values interface for internal use (includes array-based fields for UI)
|
||||
interface FormValues {
|
||||
name?: string;
|
||||
ssoEnabled?: boolean;
|
||||
ssoType?: string;
|
||||
googleAuthConfig?: AuthtypesGoogleConfigDTO & {
|
||||
domainToAdminEmailList?: Array<{ domain?: string; adminEmail?: string }>;
|
||||
};
|
||||
samlConfig?: AuthtypesSamlConfigDTO;
|
||||
oidcConfig?: AuthtypesOIDCConfigDTO;
|
||||
roleMapping?: AuthtypesRoleMappingDTO & {
|
||||
groupMappingsList?: Array<{ groupName?: string; role?: string }>;
|
||||
};
|
||||
record?: GettableAuthDomain;
|
||||
}
|
||||
|
||||
function configureAuthnProvider(
|
||||
@@ -67,299 +39,64 @@ function configureAuthnProvider(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts groupMappingsList array to groupMappings Record for API
|
||||
*/
|
||||
function convertGroupMappingsToRecord(
|
||||
groupMappingsList?: Array<{ groupName?: string; role?: string }>,
|
||||
): Record<string, string> | undefined {
|
||||
if (!Array.isArray(groupMappingsList) || groupMappingsList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const groupMappings: Record<string, string> = {};
|
||||
groupMappingsList.forEach((item) => {
|
||||
if (item.groupName && item.role) {
|
||||
groupMappings[item.groupName] = item.role;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(groupMappings).length > 0 ? groupMappings : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts groupMappings Record to groupMappingsList array for form
|
||||
*/
|
||||
function convertGroupMappingsToList(
|
||||
groupMappings?: Record<string, string> | null,
|
||||
): Array<{ groupName: string; role: string }> {
|
||||
if (!groupMappings) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(groupMappings).map(([groupName, role]) => ({
|
||||
groupName,
|
||||
role,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts domainToAdminEmailList array to domainToAdminEmail Record for API
|
||||
*/
|
||||
function convertDomainMappingsToRecord(
|
||||
domainToAdminEmailList?: Array<{ domain?: string; adminEmail?: string }>,
|
||||
): Record<string, string> | undefined {
|
||||
if (
|
||||
!Array.isArray(domainToAdminEmailList) ||
|
||||
domainToAdminEmailList.length === 0
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const domainToAdminEmail: Record<string, string> = {};
|
||||
domainToAdminEmailList.forEach((item) => {
|
||||
if (item.domain && item.adminEmail) {
|
||||
domainToAdminEmail[item.domain] = item.adminEmail;
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(domainToAdminEmail).length > 0
|
||||
? domainToAdminEmail
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts domainToAdminEmail Record to domainToAdminEmailList array for form
|
||||
*/
|
||||
function convertDomainMappingsToList(
|
||||
domainToAdminEmail?: Record<string, string>,
|
||||
): Array<{ domain: string; adminEmail: string }> {
|
||||
if (!domainToAdminEmail) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(domainToAdminEmail).map(([domain, adminEmail]) => ({
|
||||
domain,
|
||||
adminEmail,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares initial form values from API record
|
||||
*/
|
||||
function prepareInitialValues(
|
||||
record?: AuthtypesGettableAuthDomainDTO,
|
||||
): FormValues {
|
||||
if (!record) {
|
||||
return {
|
||||
name: '',
|
||||
ssoEnabled: false,
|
||||
ssoType: '',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...record,
|
||||
googleAuthConfig: record.googleAuthConfig
|
||||
? {
|
||||
...record.googleAuthConfig,
|
||||
domainToAdminEmailList: convertDomainMappingsToList(
|
||||
record.googleAuthConfig.domainToAdminEmail,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
roleMapping: record.roleMapping
|
||||
? {
|
||||
...record.roleMapping,
|
||||
groupMappingsList: convertGroupMappingsToList(
|
||||
record.roleMapping.groupMappings,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function CreateOrEdit(props: CreateOrEditProps): JSX.Element {
|
||||
const { isCreate, record, onClose } = props;
|
||||
const [form] = Form.useForm<AuthtypesPostableAuthDomainDTO>();
|
||||
const [form] = Form.useForm<PostableAuthDomain>();
|
||||
const [authnProvider, setAuthnProvider] = useState<string>(
|
||||
record?.ssoType || '',
|
||||
);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: AxiosError<RenderErrorResponseDTO>): void => {
|
||||
try {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
showErrorModal(apiError as APIError);
|
||||
}
|
||||
},
|
||||
[showErrorModal],
|
||||
);
|
||||
const samlEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const {
|
||||
mutate: createAuthDomain,
|
||||
isLoading: isCreating,
|
||||
} = useCreateAuthDomain<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const {
|
||||
mutate: updateAuthDomain,
|
||||
isLoading: isUpdating,
|
||||
} = useUpdateAuthDomain<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
/**
|
||||
* Prepares Google Auth config for API payload
|
||||
*/
|
||||
const getGoogleAuthConfig = useCallback(():
|
||||
| AuthtypesGoogleConfigDTO
|
||||
| undefined => {
|
||||
const config = form.getFieldValue('googleAuthConfig');
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { domainToAdminEmailList, ...rest } = config;
|
||||
const domainToAdminEmail = convertDomainMappingsToRecord(
|
||||
domainToAdminEmailList,
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
...(domainToAdminEmail && { domainToAdminEmail }),
|
||||
};
|
||||
}, [form]);
|
||||
|
||||
/**
|
||||
* Prepares role mapping for API payload
|
||||
*/
|
||||
const getRoleMapping = useCallback((): AuthtypesRoleMappingDTO | undefined => {
|
||||
const roleMapping = form.getFieldValue('roleMapping');
|
||||
if (!roleMapping) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { groupMappingsList, ...rest } = roleMapping;
|
||||
const groupMappings = convertGroupMappingsToRecord(groupMappingsList);
|
||||
|
||||
// Only return roleMapping if there's meaningful content
|
||||
const hasDefaultRole = rest.defaultRole && rest.defaultRole !== 'VIEWER';
|
||||
const hasUseRoleAttribute = rest.useRoleAttribute === true;
|
||||
const hasGroupMappings =
|
||||
groupMappings && Object.keys(groupMappings).length > 0;
|
||||
|
||||
if (!hasDefaultRole && !hasUseRoleAttribute && !hasGroupMappings) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
...(groupMappings && { groupMappings }),
|
||||
};
|
||||
}, [form]);
|
||||
|
||||
const onSubmitHandler = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const onSubmitHandler = async (): Promise<void> => {
|
||||
const name = form.getFieldValue('name');
|
||||
const googleAuthConfig = getGoogleAuthConfig();
|
||||
const googleAuthConfig = form.getFieldValue('googleAuthConfig');
|
||||
const samlConfig = form.getFieldValue('samlConfig');
|
||||
const oidcConfig = form.getFieldValue('oidcConfig');
|
||||
const roleMapping = getRoleMapping();
|
||||
|
||||
if (isCreate) {
|
||||
createAuthDomain(
|
||||
{
|
||||
data: {
|
||||
name,
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: authnProvider,
|
||||
googleAuthConfig,
|
||||
samlConfig,
|
||||
oidcConfig,
|
||||
roleMapping,
|
||||
},
|
||||
try {
|
||||
if (isCreate) {
|
||||
await post({
|
||||
name,
|
||||
config: {
|
||||
ssoEnabled: true,
|
||||
ssoType: authnProvider,
|
||||
googleAuthConfig,
|
||||
samlConfig,
|
||||
oidcConfig,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Domain created successfully',
|
||||
});
|
||||
onClose();
|
||||
});
|
||||
} else {
|
||||
await put({
|
||||
id: record?.id || '',
|
||||
config: {
|
||||
ssoEnabled: form.getFieldValue('ssoEnabled'),
|
||||
ssoType: authnProvider,
|
||||
googleAuthConfig,
|
||||
samlConfig,
|
||||
oidcConfig,
|
||||
},
|
||||
onError: handleError,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if (!record?.id) {
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
updateAuthDomain(
|
||||
{
|
||||
pathParams: { id: record.id },
|
||||
data: {
|
||||
config: {
|
||||
ssoEnabled: form.getFieldValue('ssoEnabled'),
|
||||
ssoType: authnProvider,
|
||||
googleAuthConfig,
|
||||
samlConfig,
|
||||
oidcConfig,
|
||||
roleMapping,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Domain updated successfully',
|
||||
});
|
||||
onClose();
|
||||
},
|
||||
onError: handleError,
|
||||
},
|
||||
);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
}, [
|
||||
authnProvider,
|
||||
createAuthDomain,
|
||||
form,
|
||||
getGoogleAuthConfig,
|
||||
getRoleMapping,
|
||||
handleError,
|
||||
isCreate,
|
||||
notifications,
|
||||
onClose,
|
||||
record,
|
||||
updateAuthDomain,
|
||||
]);
|
||||
};
|
||||
|
||||
const onBackHandler = useCallback((): void => {
|
||||
form.resetFields();
|
||||
const onBackHandler = (): void => {
|
||||
setAuthnProvider('');
|
||||
}, [form]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
width={authnProvider ? 980 : undefined}
|
||||
>
|
||||
<Modal open footer={null} onCancel={onClose}>
|
||||
<Form
|
||||
name="auth-domain"
|
||||
initialValues={defaultTo(prepareInitialValues(record), {
|
||||
initialValues={defaultTo(record, {
|
||||
name: '',
|
||||
ssoEnabled: false,
|
||||
ssoType: '',
|
||||
@@ -379,11 +116,7 @@ function CreateOrEdit(props: CreateOrEditProps): JSX.Element {
|
||||
<section className="action-buttons">
|
||||
{isCreate && <Button onClick={onBackHandler}>Back</Button>}
|
||||
{!isCreate && <Button onClick={onClose}>Cancel</Button>}
|
||||
<Button
|
||||
onClick={onSubmitHandler}
|
||||
type="primary"
|
||||
loading={isCreating || isUpdating}
|
||||
>
|
||||
<Button onClick={onSubmitHandler} type="primary">
|
||||
Save Changes
|
||||
</Button>
|
||||
</section>
|
||||
|
||||
@@ -1,46 +1,20 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Checkbox } from '@signozhq/checkbox';
|
||||
import { ChevronDown, ChevronRight, CircleHelp } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Collapse, Form, Tooltip } from 'antd';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
|
||||
import DomainMappingList from './components/DomainMappingList';
|
||||
import EmailTagInput from './components/EmailTagInput';
|
||||
import RoleMappingSection from './components/RoleMappingSection';
|
||||
import { Form, Input, Typography } from 'antd';
|
||||
|
||||
import './Providers.styles.scss';
|
||||
|
||||
type ExpandedSection = 'workspace-groups' | 'role-mapping' | null;
|
||||
|
||||
function ConfigureGoogleAuthAuthnProvider({
|
||||
isCreate,
|
||||
}: {
|
||||
isCreate: boolean;
|
||||
}): JSX.Element {
|
||||
const form = Form.useFormInstance();
|
||||
const fetchGroups = Form.useWatch(['googleAuthConfig', 'fetchGroups'], form);
|
||||
|
||||
const [expandedSection, setExpandedSection] = useState<ExpandedSection>(null);
|
||||
|
||||
const handleWorkspaceGroupsChange = useCallback(
|
||||
(keys: string | string[]): void => {
|
||||
const isExpanding = Array.isArray(keys) ? keys.length > 0 : !!keys;
|
||||
setExpandedSection(isExpanding ? 'workspace-groups' : null);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleRoleMappingChange = useCallback((expanded: boolean): void => {
|
||||
setExpandedSection(expanded ? 'role-mapping' : null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="google-auth">
|
||||
<section className="google-auth__header">
|
||||
<h3 className="google-auth__title">Edit Google Authentication</h3>
|
||||
<p className="google-auth__description">
|
||||
<section className="header">
|
||||
<Typography.Text className="title">
|
||||
Edit Google Authentication
|
||||
</Typography.Text>
|
||||
<Typography.Paragraph className="description">
|
||||
Enter OAuth 2.0 credentials obtained from the Google API Console below.
|
||||
Read the{' '}
|
||||
<a
|
||||
@@ -51,234 +25,50 @@ function ConfigureGoogleAuthAuthnProvider({
|
||||
docs
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</p>
|
||||
</Typography.Paragraph>
|
||||
</section>
|
||||
|
||||
<div className="google-auth__columns">
|
||||
{/* Left Column - Core OAuth Settings */}
|
||||
<div className="google-auth__left">
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="google-domain">
|
||||
Domain
|
||||
<Tooltip title="The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name="name"
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Domain is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="google-domain" disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
className="field"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="google-client-id">
|
||||
Client ID
|
||||
<Tooltip title="ClientID is the application's ID. For example, 292085223830.apps.googleusercontent.com.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'clientId']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Client ID is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="google-client-id" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Client ID"
|
||||
name={['googleAuthConfig', 'clientId']}
|
||||
className="field"
|
||||
tooltip={{
|
||||
title: `ClientID is the application's ID. For example, 292085223830.apps.googleusercontent.com.`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="google-client-secret">
|
||||
Client Secret
|
||||
<Tooltip title="It is the application's secret.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'clientSecret']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Client Secret is required',
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input id="google-client-secret" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Client Secret"
|
||||
name={['googleAuthConfig', 'clientSecret']}
|
||||
className="field"
|
||||
tooltip={{
|
||||
title: `It is the application's secret.`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'insecureSkipEmailVerified']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="google-skip-email-verification"
|
||||
labelName="Skip Email Verification"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(
|
||||
['googleAuthConfig', 'insecureSkipEmailVerified'],
|
||||
checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title='Whether to skip email verification. Defaults to "false"'>
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="Google OAuth2 won't be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Google Workspace Groups (Advanced) */}
|
||||
<div className="google-auth__right">
|
||||
<Collapse
|
||||
bordered={false}
|
||||
activeKey={
|
||||
expandedSection === 'workspace-groups' ? ['workspace-groups'] : []
|
||||
}
|
||||
onChange={handleWorkspaceGroupsChange}
|
||||
className="google-auth__collapse"
|
||||
expandIcon={(): null => null}
|
||||
>
|
||||
<Collapse.Panel
|
||||
key="workspace-groups"
|
||||
header={
|
||||
<div className="google-auth__collapse-header">
|
||||
{expandedSection !== 'workspace-groups' ? (
|
||||
<ChevronRight size={16} />
|
||||
) : (
|
||||
<ChevronDown size={16} />
|
||||
)}
|
||||
<div className="google-auth__collapse-header-text">
|
||||
<h4 className="google-auth__section-title">
|
||||
Google Workspace Groups (Advanced)
|
||||
</h4>
|
||||
<p className="google-auth__section-description">
|
||||
Enable group fetching to retrieve user groups from Google Workspace.
|
||||
Requires a Service Account with domain-wide delegation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="google-auth__group-content">
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'fetchGroups']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="google-fetch-groups"
|
||||
labelName="Fetch Groups"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(['googleAuthConfig', 'fetchGroups'], checked);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="Enable fetching Google Workspace groups for the user. Requires service account configuration.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{fetchGroups && (
|
||||
<div className="google-auth__group-fields">
|
||||
<div className="google-auth__field-group">
|
||||
<label
|
||||
className="google-auth__label"
|
||||
htmlFor="google-service-account-json"
|
||||
>
|
||||
Service Account JSON
|
||||
<Tooltip title="The JSON content of the Google Service Account credentials file. Required for group fetching.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'serviceAccountJson']}
|
||||
className="google-auth__form-item"
|
||||
>
|
||||
<TextArea
|
||||
id="google-service-account-json"
|
||||
rows={3}
|
||||
placeholder="Paste service account JSON"
|
||||
className="google-auth__textarea"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
<DomainMappingList
|
||||
fieldNamePrefix={['googleAuthConfig', 'domainToAdminEmailList']}
|
||||
/>
|
||||
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'fetchTransitiveGroupMembership']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="google-transitive-membership"
|
||||
labelName="Fetch Transitive Group Membership"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(
|
||||
['googleAuthConfig', 'fetchTransitiveGroupMembership'],
|
||||
checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="If enabled, recursively fetch groups that contain other groups (transitive membership).">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label
|
||||
className="google-auth__label"
|
||||
htmlFor="google-allowed-groups"
|
||||
>
|
||||
Allowed Groups
|
||||
<Tooltip title="Optional list of allowed groups. If configured, only users belonging to one of these groups will be allowed to login.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['googleAuthConfig', 'allowedGroups']}
|
||||
className="google-auth__form-item"
|
||||
>
|
||||
<EmailTagInput placeholder="Type a group email and press Enter" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
<RoleMappingSection
|
||||
fieldNamePrefix={['roleMapping']}
|
||||
isExpanded={expandedSection === 'role-mapping'}
|
||||
onExpandChange={handleRoleMappingChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="Google OAuth2 won’t be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,211 +1,110 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Checkbox } from '@signozhq/checkbox';
|
||||
import { CircleHelp } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Form, Tooltip } from 'antd';
|
||||
|
||||
import ClaimMappingSection from './components/ClaimMappingSection';
|
||||
import RoleMappingSection from './components/RoleMappingSection';
|
||||
import { Checkbox, Form, Input, Typography } from 'antd';
|
||||
|
||||
import './Providers.styles.scss';
|
||||
|
||||
type ExpandedSection = 'claim-mapping' | 'role-mapping' | null;
|
||||
|
||||
function ConfigureOIDCAuthnProvider({
|
||||
isCreate,
|
||||
}: {
|
||||
isCreate: boolean;
|
||||
}): JSX.Element {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const [expandedSection, setExpandedSection] = useState<ExpandedSection>(null);
|
||||
|
||||
const handleClaimMappingChange = useCallback((expanded: boolean): void => {
|
||||
setExpandedSection(expanded ? 'claim-mapping' : null);
|
||||
}, []);
|
||||
|
||||
const handleRoleMappingChange = useCallback((expanded: boolean): void => {
|
||||
setExpandedSection(expanded ? 'role-mapping' : null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="google-auth">
|
||||
<section className="google-auth__header">
|
||||
<h3 className="google-auth__title">Edit OIDC Authentication</h3>
|
||||
<p className="google-auth__description">
|
||||
Configure OpenID Connect Single Sign-On with your Identity Provider. Read
|
||||
the{' '}
|
||||
<a
|
||||
href="https://signoz.io/docs/userguide/sso-authentication"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
docs
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</p>
|
||||
<div className="saml">
|
||||
<section className="header">
|
||||
<Typography.Text className="title">
|
||||
Edit OIDC Authentication
|
||||
</Typography.Text>
|
||||
</section>
|
||||
|
||||
<div className="google-auth__columns">
|
||||
{/* Left Column - Core OIDC Settings */}
|
||||
<div className="google-auth__left">
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="oidc-domain">
|
||||
Domain
|
||||
<Tooltip title="The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name="name"
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Domain is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="oidc-domain" disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="oidc-issuer">
|
||||
Issuer URL
|
||||
<Tooltip title='The URL identifier for the OIDC provider. For example: "https://accounts.google.com" or "https://login.salesforce.com".'>
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'issuer']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Issuer URL is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="oidc-issuer" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Issuer URL"
|
||||
name={['oidcConfig', 'issuer']}
|
||||
tooltip={{
|
||||
title: `It is the URL identifier for the service. For example: "https://accounts.google.com" or "https://login.salesforce.com".`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="oidc-issuer-alias">
|
||||
Issuer Alias
|
||||
<Tooltip title="Optional: Override the issuer URL from .well-known/openid-configuration for providers like Azure or Oracle IDCS.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'issuerAlias']}
|
||||
className="google-auth__form-item"
|
||||
>
|
||||
<Input id="oidc-issuer-alias" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Issuer Alias"
|
||||
name={['oidcConfig', 'issuerAlias']}
|
||||
tooltip={{
|
||||
title: `Some offspec providers like Azure, Oracle IDCS have oidc discovery url different from issuer url which causes issuerValidation to fail.
|
||||
This provides a way to override the Issuer url from the .well-known/openid-configuration issuer`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="oidc-client-id">
|
||||
Client ID
|
||||
<Tooltip title="The application's client ID from your OIDC provider.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'clientId']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Client ID is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="oidc-client-id" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Client ID"
|
||||
name={['oidcConfig', 'clientId']}
|
||||
tooltip={{ title: `It is the application's ID.` }}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="oidc-client-secret">
|
||||
Client Secret
|
||||
<Tooltip title="The application's client secret from your OIDC provider.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'clientSecret']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Client Secret is required',
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input id="oidc-client-secret" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Client Secret"
|
||||
name={['oidcConfig', 'clientSecret']}
|
||||
tooltip={{ title: `It is the application's secret.` }}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'insecureSkipEmailVerified']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="oidc-skip-email-verification"
|
||||
labelName="Skip Email Verification"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(
|
||||
['oidcConfig', 'insecureSkipEmailVerified'],
|
||||
checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title='Whether to skip email verification. Defaults to "false"'>
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Email Claim Mapping"
|
||||
name={['oidcConfig', 'claimMapping', 'email']}
|
||||
tooltip={{
|
||||
title: `Mapping of email claims to the corresponding email field in the token.`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['oidcConfig', 'getUserInfo']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="oidc-get-user-info"
|
||||
labelName="Get User Info"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(['oidcConfig', 'getUserInfo'], checked);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="Use the userinfo endpoint to get additional claims. Useful when providers return thin ID tokens.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Skip Email Verification"
|
||||
name={['oidcConfig', 'insecureSkipEmailVerified']}
|
||||
valuePropName="checked"
|
||||
className="field"
|
||||
tooltip={{
|
||||
title: `Whether to skip email verification. Defaults to "false"`,
|
||||
}}
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="OIDC won't be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Get User Info"
|
||||
name={['oidcConfig', 'getUserInfo']}
|
||||
valuePropName="checked"
|
||||
className="field"
|
||||
tooltip={{
|
||||
title: `Uses the userinfo endpoint to get additional claims for the token. This is especially useful where upstreams return "thin" id tokens`,
|
||||
}}
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
{/* Right Column - Advanced Settings */}
|
||||
<div className="google-auth__right">
|
||||
<ClaimMappingSection
|
||||
fieldNamePrefix={['oidcConfig', 'claimMapping']}
|
||||
isExpanded={expandedSection === 'claim-mapping'}
|
||||
onExpandChange={handleClaimMappingChange}
|
||||
/>
|
||||
|
||||
<RoleMappingSection
|
||||
fieldNamePrefix={['roleMapping']}
|
||||
isExpanded={expandedSection === 'role-mapping'}
|
||||
onExpandChange={handleRoleMappingChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="OIDC won’t be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,190 +1,82 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Checkbox } from '@signozhq/checkbox';
|
||||
import { CircleHelp } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Form, Tooltip } from 'antd';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
|
||||
import AttributeMappingSection from './components/AttributeMappingSection';
|
||||
import RoleMappingSection from './components/RoleMappingSection';
|
||||
import { Checkbox, Form, Input, Typography } from 'antd';
|
||||
|
||||
import './Providers.styles.scss';
|
||||
|
||||
type ExpandedSection = 'attribute-mapping' | 'role-mapping' | null;
|
||||
|
||||
function ConfigureSAMLAuthnProvider({
|
||||
isCreate,
|
||||
}: {
|
||||
isCreate: boolean;
|
||||
}): JSX.Element {
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const [expandedSection, setExpandedSection] = useState<ExpandedSection>(null);
|
||||
|
||||
const handleAttributeMappingChange = useCallback((expanded: boolean): void => {
|
||||
setExpandedSection(expanded ? 'attribute-mapping' : null);
|
||||
}, []);
|
||||
|
||||
const handleRoleMappingChange = useCallback((expanded: boolean): void => {
|
||||
setExpandedSection(expanded ? 'role-mapping' : null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="google-auth">
|
||||
<section className="google-auth__header">
|
||||
<h3 className="google-auth__title">Edit SAML Authentication</h3>
|
||||
<p className="google-auth__description">
|
||||
Configure SAML 2.0 Single Sign-On with your Identity Provider. Read the{' '}
|
||||
<a
|
||||
href="https://signoz.io/docs/userguide/sso-authentication"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
docs
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</p>
|
||||
<div className="saml">
|
||||
<section className="header">
|
||||
<Typography.Text className="title">
|
||||
Edit SAML Authentication
|
||||
</Typography.Text>
|
||||
</section>
|
||||
|
||||
<div className="google-auth__columns">
|
||||
{/* Left Column - Core SAML Settings */}
|
||||
<div className="google-auth__left">
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="saml-domain">
|
||||
Domain
|
||||
<Tooltip title="The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name="name"
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{ required: true, message: 'Domain is required', whitespace: true },
|
||||
]}
|
||||
>
|
||||
<Input id="saml-domain" disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Domain"
|
||||
name="name"
|
||||
tooltip={{
|
||||
title:
|
||||
'The email domain for users who should use SSO (e.g., `example.com` for users with `@example.com` emails)',
|
||||
}}
|
||||
>
|
||||
<Input disabled={!isCreate} />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="saml-acs-url">
|
||||
SAML ACS URL
|
||||
<Tooltip title="The SSO endpoint of the SAML identity provider. It can typically be found in the SingleSignOnService element in the SAML metadata of the identity provider.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['samlConfig', 'samlIdp']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'SAML ACS URL is required',
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input id="saml-acs-url" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="SAML ACS URL"
|
||||
name={['samlConfig', 'samlIdp']}
|
||||
tooltip={{
|
||||
title: `The SSO endpoint of the SAML identity provider. It can typically be found in the SingleSignOnService element in the SAML metadata of the identity provider. Example: <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="{samlIdp}"/>`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="saml-entity-id">
|
||||
SAML Entity ID
|
||||
<Tooltip title="The entityID of the SAML identity provider. It can typically be found in the EntityID attribute of the EntityDescriptor element in the SAML metadata.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['samlConfig', 'samlEntity']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'SAML Entity ID is required',
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input id="saml-entity-id" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="SAML Entity ID"
|
||||
name={['samlConfig', 'samlEntity']}
|
||||
tooltip={{
|
||||
title: `The entityID of the SAML identity provider. It can typically be found in the EntityID attribute of the EntityDescriptor element in the SAML metadata of the identity provider. Example: <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="{samlEntity}">`,
|
||||
}}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__field-group">
|
||||
<label className="google-auth__label" htmlFor="saml-certificate">
|
||||
SAML X.509 Certificate
|
||||
<Tooltip title="The certificate of the SAML identity provider. It can typically be found in the X509Certificate element in the SAML metadata.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={['samlConfig', 'samlCert']}
|
||||
className="google-auth__form-item"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'SAML Certificate is required',
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TextArea
|
||||
id="saml-certificate"
|
||||
rows={3}
|
||||
placeholder="Paste X.509 certificate"
|
||||
className="google-auth__textarea"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="SAML X.509 Certificate"
|
||||
name={['samlConfig', 'samlCert']}
|
||||
tooltip={{
|
||||
title: `The certificate of the SAML identity provider. It can typically be found in the X509Certificate element in the SAML metadata of the identity provider. Example: <ds:X509Certificate><ds:X509Certificate>{samlCert}</ds:X509Certificate></ds:X509Certificate>`,
|
||||
}}
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<div className="google-auth__checkbox-row">
|
||||
<Form.Item
|
||||
name={['samlConfig', 'insecureSkipAuthNRequestsSigned']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="saml-skip-signing"
|
||||
labelName="Skip Signing AuthN Requests"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue(
|
||||
['samlConfig', 'insecureSkipAuthNRequestsSigned'],
|
||||
checked,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="Whether to skip signing the SAML requests. For providers like JumpCloud, this should be enabled.">
|
||||
<CircleHelp size={14} className="google-auth__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="Skip Signing AuthN Requests"
|
||||
name={['samlConfig', 'insecureSkipAuthNRequestsSigned']}
|
||||
valuePropName="checked"
|
||||
className="field"
|
||||
tooltip={{
|
||||
title: `Whether to skip signing the SAML requests. It can typically be found in the WantAuthnRequestsSigned attribute of the IDPSSODescriptor element in the SAML metadata of the identity provider. Example: <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||
For providers like jumpcloud, this should be set to true.Note: This is the reverse of WantAuthnRequestsSigned. If WantAuthnRequestsSigned is false, then InsecureSkipAuthNRequestsSigned should be true.`,
|
||||
}}
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="SAML won't be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Right Column - Advanced Settings */}
|
||||
<div className="google-auth__right">
|
||||
<AttributeMappingSection
|
||||
fieldNamePrefix={['samlConfig', 'attributeMapping']}
|
||||
isExpanded={expandedSection === 'attribute-mapping'}
|
||||
onExpandChange={handleAttributeMappingChange}
|
||||
/>
|
||||
|
||||
<RoleMappingSection
|
||||
fieldNamePrefix={['roleMapping']}
|
||||
isExpanded={expandedSection === 'role-mapping'}
|
||||
onExpandChange={handleRoleMappingChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
showIcon
|
||||
description="SAML won’t be enabled unless you enter all the attributes above"
|
||||
className="callout"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,243 +2,23 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
.ant-form-item {
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin: 0;
|
||||
color: var(--l2-foreground);
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
|
||||
a {
|
||||
color: var(--accent-primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__columns {
|
||||
display: grid;
|
||||
grid-template-columns: 0.9fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__right {
|
||||
border-left: 1px solid var(--l3-border);
|
||||
padding-left: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__field-group {
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__label-icon {
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&__checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
height: 32px;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground) !important;
|
||||
opacity: 1;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l3-border) !important;
|
||||
.description {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: auto;
|
||||
}
|
||||
&__textarea {
|
||||
min-height: 60px !important;
|
||||
max-height: 200px;
|
||||
resize: vertical;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
font-family: 'SF Mono', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground) !important;
|
||||
font-family: Inter, sans-serif;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l3-border) !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
button[role='checkbox'] {
|
||||
border: 1px solid var(--l2-foreground) !important;
|
||||
border-radius: 2px;
|
||||
|
||||
&[data-state='checked'] {
|
||||
background-color: var(--bg-robin-500) !important;
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse {
|
||||
background: transparent !important;
|
||||
|
||||
.ant-collapse-item {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
border-top: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px 0 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
color: var(--l3-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__section-description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__group-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__group-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
|
||||
.google-auth__field-group,
|
||||
.google-auth__checkbox-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--l3-foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--l3-foreground) transparent;
|
||||
}
|
||||
|
||||
.callout {
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
.attribute-mapping-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__collapse {
|
||||
background: transparent !important;
|
||||
|
||||
.ant-collapse-item {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
border-top: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px 0 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
color: var(--l3-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__section-description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
|
||||
// Thin scrollbar
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--l3-foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--l3-foreground) transparent;
|
||||
}
|
||||
|
||||
&__field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__label-icon {
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l3-border) !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, CircleHelp } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Collapse, Form, Tooltip } from 'antd';
|
||||
|
||||
import './AttributeMappingSection.styles.scss';
|
||||
|
||||
interface AttributeMappingSectionProps {
|
||||
/** The form field name prefix for the attribute mapping configuration */
|
||||
fieldNamePrefix: string[];
|
||||
/** Whether the section is expanded (controlled mode) */
|
||||
isExpanded?: boolean;
|
||||
/** Callback when expand/collapse is toggled */
|
||||
onExpandChange?: (expanded: boolean) => void;
|
||||
}
|
||||
|
||||
function AttributeMappingSection({
|
||||
fieldNamePrefix,
|
||||
isExpanded,
|
||||
onExpandChange,
|
||||
}: AttributeMappingSectionProps): JSX.Element {
|
||||
// Support both controlled and uncontrolled modes
|
||||
const [internalExpanded, setInternalExpanded] = useState(false);
|
||||
const isControlled = isExpanded !== undefined;
|
||||
const expanded = isControlled ? isExpanded : internalExpanded;
|
||||
|
||||
const handleCollapseChange = useCallback(
|
||||
(keys: string | string[]): void => {
|
||||
const newExpanded = Array.isArray(keys) ? keys.length > 0 : !!keys;
|
||||
if (isControlled && onExpandChange) {
|
||||
onExpandChange(newExpanded);
|
||||
} else {
|
||||
setInternalExpanded(newExpanded);
|
||||
}
|
||||
},
|
||||
[isControlled, onExpandChange],
|
||||
);
|
||||
|
||||
const collapseActiveKey = expanded ? ['attribute-mapping'] : [];
|
||||
|
||||
return (
|
||||
<div className="attribute-mapping-section">
|
||||
<Collapse
|
||||
bordered={false}
|
||||
activeKey={collapseActiveKey}
|
||||
onChange={handleCollapseChange}
|
||||
className="attribute-mapping-section__collapse"
|
||||
expandIcon={(): null => null}
|
||||
>
|
||||
<Collapse.Panel
|
||||
key="attribute-mapping"
|
||||
header={
|
||||
<div className="attribute-mapping-section__collapse-header">
|
||||
{!expanded ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
<div className="attribute-mapping-section__collapse-header-text">
|
||||
<h4 className="attribute-mapping-section__section-title">
|
||||
Attribute Mapping (Advanced)
|
||||
</h4>
|
||||
<p className="attribute-mapping-section__section-description">
|
||||
Configure how SAML assertion attributes from your Identity Provider map
|
||||
to SigNoz user attributes. Leave empty to use default values. Note:
|
||||
Email is always extracted from the NameID assertion.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="attribute-mapping-section__content">
|
||||
{/* Name Attribute */}
|
||||
<div className="attribute-mapping-section__field-group">
|
||||
<label
|
||||
className="attribute-mapping-section__label"
|
||||
htmlFor="name-attribute"
|
||||
>
|
||||
Name Attribute
|
||||
<Tooltip title="The SAML attribute key that contains the user's display name. Default: 'name'">
|
||||
<CircleHelp
|
||||
size={14}
|
||||
className="attribute-mapping-section__label-icon"
|
||||
/>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'name']}
|
||||
className="attribute-mapping-section__form-item"
|
||||
>
|
||||
<Input id="name-attribute" placeholder="Name" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Groups Attribute */}
|
||||
<div className="attribute-mapping-section__field-group">
|
||||
<label
|
||||
className="attribute-mapping-section__label"
|
||||
htmlFor="groups-attribute"
|
||||
>
|
||||
Groups Attribute
|
||||
<Tooltip title="The SAML attribute key that contains the user's group memberships. Used for role mapping. Default: 'groups'">
|
||||
<CircleHelp
|
||||
size={14}
|
||||
className="attribute-mapping-section__label-icon"
|
||||
/>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'groups']}
|
||||
className="attribute-mapping-section__form-item"
|
||||
>
|
||||
<Input id="groups-attribute" placeholder="Groups" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Role Attribute */}
|
||||
<div className="attribute-mapping-section__field-group">
|
||||
<label
|
||||
className="attribute-mapping-section__label"
|
||||
htmlFor="role-attribute"
|
||||
>
|
||||
Role Attribute
|
||||
<Tooltip title="The SAML attribute key that contains the user's role directly from the IDP. Default: 'role'">
|
||||
<CircleHelp
|
||||
size={14}
|
||||
className="attribute-mapping-section__label-icon"
|
||||
/>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'role']}
|
||||
className="attribute-mapping-section__form-item"
|
||||
>
|
||||
<Input id="role-attribute" placeholder="Role" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AttributeMappingSection;
|
||||
@@ -1,131 +0,0 @@
|
||||
.claim-mapping-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__collapse {
|
||||
background: transparent !important;
|
||||
|
||||
.ant-collapse-item {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
border-top: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px 0 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
color: var(--l3-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__section-description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
|
||||
// Thin scrollbar
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--l3-foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--l3-foreground) transparent;
|
||||
}
|
||||
|
||||
&__field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__label-icon {
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l3-border) !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, CircleHelp } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Collapse, Form, Tooltip } from 'antd';
|
||||
|
||||
import './ClaimMappingSection.styles.scss';
|
||||
|
||||
interface ClaimMappingSectionProps {
|
||||
/** The form field name prefix for the claim mapping configuration */
|
||||
fieldNamePrefix: string[];
|
||||
/** Whether the section is expanded (controlled mode) */
|
||||
isExpanded?: boolean;
|
||||
/** Callback when expand/collapse is toggled */
|
||||
onExpandChange?: (expanded: boolean) => void;
|
||||
}
|
||||
|
||||
function ClaimMappingSection({
|
||||
fieldNamePrefix,
|
||||
isExpanded,
|
||||
onExpandChange,
|
||||
}: ClaimMappingSectionProps): JSX.Element {
|
||||
// Support both controlled and uncontrolled modes
|
||||
const [internalExpanded, setInternalExpanded] = useState(false);
|
||||
const isControlled = isExpanded !== undefined;
|
||||
const expanded = isControlled ? isExpanded : internalExpanded;
|
||||
|
||||
const handleCollapseChange = useCallback(
|
||||
(keys: string | string[]): void => {
|
||||
const newExpanded = Array.isArray(keys) ? keys.length > 0 : !!keys;
|
||||
if (isControlled && onExpandChange) {
|
||||
onExpandChange(newExpanded);
|
||||
} else {
|
||||
setInternalExpanded(newExpanded);
|
||||
}
|
||||
},
|
||||
[isControlled, onExpandChange],
|
||||
);
|
||||
|
||||
const collapseActiveKey = expanded ? ['claim-mapping'] : [];
|
||||
|
||||
return (
|
||||
<div className="claim-mapping-section">
|
||||
<Collapse
|
||||
bordered={false}
|
||||
activeKey={collapseActiveKey}
|
||||
onChange={handleCollapseChange}
|
||||
className="claim-mapping-section__collapse"
|
||||
expandIcon={(): null => null}
|
||||
>
|
||||
<Collapse.Panel
|
||||
key="claim-mapping"
|
||||
header={
|
||||
<div className="claim-mapping-section__collapse-header">
|
||||
{!expanded ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
<div className="claim-mapping-section__collapse-header-text">
|
||||
<h4 className="claim-mapping-section__section-title">
|
||||
Claim Mapping (Advanced)
|
||||
</h4>
|
||||
<p className="claim-mapping-section__section-description">
|
||||
Configure how claims from your Identity Provider map to SigNoz user
|
||||
attributes. Leave empty to use default values.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="claim-mapping-section__content">
|
||||
{/* Email Claim */}
|
||||
<div className="claim-mapping-section__field-group">
|
||||
<label className="claim-mapping-section__label" htmlFor="email-claim">
|
||||
Email Claim
|
||||
<Tooltip title="The claim key that contains the user's email address. Default: 'email'">
|
||||
<CircleHelp size={14} className="claim-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'email']}
|
||||
className="claim-mapping-section__form-item"
|
||||
>
|
||||
<Input id="email-claim" placeholder="Email" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Name Claim */}
|
||||
<div className="claim-mapping-section__field-group">
|
||||
<label className="claim-mapping-section__label" htmlFor="name-claim">
|
||||
Name Claim
|
||||
<Tooltip title="The claim key that contains the user's display name. Default: 'name'">
|
||||
<CircleHelp size={14} className="claim-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'name']}
|
||||
className="claim-mapping-section__form-item"
|
||||
>
|
||||
<Input id="name-claim" placeholder="Name" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Groups Claim */}
|
||||
<div className="claim-mapping-section__field-group">
|
||||
<label className="claim-mapping-section__label" htmlFor="groups-claim">
|
||||
Groups Claim
|
||||
<Tooltip title="The claim key that contains the user's group memberships. Used for role mapping. Default: 'groups'">
|
||||
<CircleHelp size={14} className="claim-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'groups']}
|
||||
className="claim-mapping-section__form-item"
|
||||
>
|
||||
<Input id="groups-claim" placeholder="Groups" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Role Claim */}
|
||||
<div className="claim-mapping-section__field-group">
|
||||
<label className="claim-mapping-section__label" htmlFor="role-claim">
|
||||
Role Claim
|
||||
<Tooltip title="The claim key that contains the user's role directly from the IDP. Default: 'role'">
|
||||
<CircleHelp size={14} className="claim-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'role']}
|
||||
className="claim-mapping-section__form-item"
|
||||
>
|
||||
<Input id="role-claim" placeholder="Role" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClaimMappingSection;
|
||||
@@ -1,103 +0,0 @@
|
||||
.domain-mapping-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__remove-btn {
|
||||
flex-shrink: 0;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
min-width: 32px !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
border-radius: 2px !important;
|
||||
background: transparent !important;
|
||||
color: var(--bg-cherry-500) !important;
|
||||
opacity: 0.6 !important;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, opacity 0.2s;
|
||||
box-shadow: none !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(229, 72, 77, 0.1) !important;
|
||||
opacity: 0.9 !important;
|
||||
color: var(--bg-cherry-500) !important;
|
||||
|
||||
svg {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7 !important;
|
||||
background: rgba(229, 72, 77, 0.15) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__add-btn {
|
||||
width: 100%;
|
||||
|
||||
// Ensure icon is visible
|
||||
svg,
|
||||
[class*='icon'] {
|
||||
color: var(--l2-foreground) !important;
|
||||
display: inline-block !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
svg,
|
||||
[class*='icon'] {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Plus, Trash2 } from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Form } from 'antd';
|
||||
|
||||
import './DomainMappingList.styles.scss';
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
interface DomainMappingListProps {
|
||||
/** The form field name prefix for the domain mapping list */
|
||||
fieldNamePrefix: string[];
|
||||
}
|
||||
|
||||
function DomainMappingList({
|
||||
fieldNamePrefix,
|
||||
}: DomainMappingListProps): JSX.Element {
|
||||
const validateEmail = useCallback(
|
||||
(_: unknown, value: string): Promise<void> => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('Admin email is required'));
|
||||
}
|
||||
if (!EMAIL_REGEX.test(value)) {
|
||||
return Promise.reject(new Error('Please enter a valid email'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="domain-mapping-list">
|
||||
<div className="domain-mapping-list__header">
|
||||
<span className="domain-mapping-list__title">
|
||||
Domain to Admin Email Mapping
|
||||
</span>
|
||||
<p className="domain-mapping-list__description">
|
||||
Map workspace domains to admin emails for service account impersonation.
|
||||
Use "*" as a wildcard for any domain.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.List name={fieldNamePrefix}>
|
||||
{(fields, { add, remove }): JSX.Element => (
|
||||
<div className="domain-mapping-list__items">
|
||||
{fields.map((field) => (
|
||||
<div key={field.key} className="domain-mapping-list__row">
|
||||
<Form.Item
|
||||
name={[field.name, 'domain']}
|
||||
className="domain-mapping-list__field"
|
||||
rules={[{ required: true, message: 'Domain is required' }]}
|
||||
>
|
||||
<Input placeholder="Domain (e.g., example.com or *)" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[field.name, 'adminEmail']}
|
||||
className="domain-mapping-list__field"
|
||||
rules={[{ validator: validateEmail }]}
|
||||
>
|
||||
<Input placeholder="Admin Email" />
|
||||
</Form.Item>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
className="domain-mapping-list__remove-btn"
|
||||
onClick={(): void => remove(field.name)}
|
||||
aria-label="Remove mapping"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="dashed"
|
||||
onClick={(): void => add({ domain: '', adminEmail: '' })}
|
||||
prefixIcon={<Plus size={14} />}
|
||||
className="domain-mapping-list__add-btn"
|
||||
>
|
||||
Add Domain Mapping
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DomainMappingList;
|
||||
@@ -1,28 +0,0 @@
|
||||
.email-tag-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&__select {
|
||||
width: 100%;
|
||||
|
||||
.ant-select-selector {
|
||||
.ant-select-selection-search {
|
||||
input {
|
||||
height: 32px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none !important;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__error {
|
||||
margin: 0;
|
||||
color: var(--bg-cherry-500);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Select, Tooltip } from 'antd';
|
||||
|
||||
import './EmailTagInput.styles.scss';
|
||||
|
||||
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
interface EmailTagInputProps {
|
||||
/** Current value (array of email strings) */
|
||||
value?: string[];
|
||||
/** Change handler */
|
||||
onChange?: (value: string[]) => void;
|
||||
/** Placeholder text */
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
function EmailTagInput({
|
||||
value = [],
|
||||
onChange,
|
||||
placeholder = 'Type an email and press Enter',
|
||||
}: EmailTagInputProps): JSX.Element {
|
||||
const [validationError, setValidationError] = useState('');
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newValues: string[]): void => {
|
||||
const addedValues = newValues.filter((v) => !value.includes(v));
|
||||
const invalidEmail = addedValues.find((v) => !EMAIL_REGEX.test(v));
|
||||
|
||||
if (invalidEmail) {
|
||||
setValidationError(`"${invalidEmail}" is not a valid email`);
|
||||
return;
|
||||
}
|
||||
setValidationError('');
|
||||
onChange?.(newValues);
|
||||
},
|
||||
[onChange, value],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="email-tag-input">
|
||||
<Tooltip
|
||||
title={validationError}
|
||||
open={!!validationError}
|
||||
placement="topRight"
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
tokenSeparators={[',', ' ']}
|
||||
className="email-tag-input__select"
|
||||
allowClear
|
||||
status={validationError ? 'error' : undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmailTagInput;
|
||||
@@ -1,292 +0,0 @@
|
||||
.role-mapping-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 24px;
|
||||
|
||||
&__collapse {
|
||||
background: transparent !important;
|
||||
|
||||
.ant-collapse-item {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.ant-collapse-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
border-top: none !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 12px 0 0 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
color: var(--l3-foreground);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__collapse-header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__section-title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__section-description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-height: 45vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
|
||||
// Thin scrollbar
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--l3-foreground);
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--l3-foreground) transparent;
|
||||
}
|
||||
|
||||
&__field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__label-icon {
|
||||
color: var(--l3-foreground);
|
||||
cursor: help;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
&__checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__select {
|
||||
width: 100%;
|
||||
|
||||
&.ant-select {
|
||||
.ant-select-selector {
|
||||
height: 32px;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .ant-select-selector {
|
||||
border-color: var(--l3-border) !important;
|
||||
}
|
||||
|
||||
&.ant-select-focused .ant-select-selector {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__group-mappings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__group-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&__group-title {
|
||||
margin: 0;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__group-description {
|
||||
margin: 0;
|
||||
color: var(--l3-foreground);
|
||||
}
|
||||
|
||||
&__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__field {
|
||||
&--group {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
&--role {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
&__remove-btn {
|
||||
flex-shrink: 0;
|
||||
width: 32px !important;
|
||||
height: 32px !important;
|
||||
min-width: 32px !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
border-radius: 2px !important;
|
||||
background: transparent !important;
|
||||
color: var(--bg-cherry-500) !important;
|
||||
opacity: 0.6 !important;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, opacity 0.2s;
|
||||
box-shadow: none !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(229, 72, 77, 0.1) !important;
|
||||
opacity: 0.9 !important;
|
||||
color: var(--bg-cherry-500) !important;
|
||||
|
||||
svg {
|
||||
color: var(--bg-cherry-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7 !important;
|
||||
background: rgba(229, 72, 77, 0.15) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__add-btn {
|
||||
width: 100%;
|
||||
|
||||
// Ensure icon is visible
|
||||
svg,
|
||||
[class*='icon'] {
|
||||
color: var(--l2-foreground) !important;
|
||||
display: inline-block !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
svg,
|
||||
[class*='icon'] {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Checkbox border visibility ---
|
||||
button[role='checkbox'] {
|
||||
border: 1px solid var(--l2-foreground) !important;
|
||||
border-radius: 2px;
|
||||
|
||||
&[data-state='checked'] {
|
||||
background-color: var(--bg-robin-500) !important;
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Input styles ---
|
||||
input {
|
||||
height: 32px;
|
||||
background: var(--l3-background) !important;
|
||||
border: 1px solid var(--l3-border) !important;
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l3-foreground) !important;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l3-border) !important;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
border-color: var(--bg-robin-500) !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Checkbox } from '@signozhq/checkbox';
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
CircleHelp,
|
||||
Plus,
|
||||
Trash2,
|
||||
} from '@signozhq/icons';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Collapse, Form, Select, Tooltip } from 'antd';
|
||||
|
||||
import './RoleMappingSection.styles.scss';
|
||||
|
||||
const ROLE_OPTIONS = [
|
||||
{ value: 'VIEWER', label: 'VIEWER' },
|
||||
{ value: 'EDITOR', label: 'EDITOR' },
|
||||
{ value: 'ADMIN', label: 'ADMIN' },
|
||||
];
|
||||
|
||||
interface RoleMappingSectionProps {
|
||||
/** The form field name prefix for the role mapping configuration */
|
||||
fieldNamePrefix: string[];
|
||||
/** Whether the section is expanded (controlled mode) */
|
||||
isExpanded?: boolean;
|
||||
/** Callback when expand/collapse is toggled */
|
||||
onExpandChange?: (expanded: boolean) => void;
|
||||
}
|
||||
|
||||
function RoleMappingSection({
|
||||
fieldNamePrefix,
|
||||
isExpanded,
|
||||
onExpandChange,
|
||||
}: RoleMappingSectionProps): JSX.Element {
|
||||
const form = Form.useFormInstance();
|
||||
const useRoleAttribute = Form.useWatch(
|
||||
[...fieldNamePrefix, 'useRoleAttribute'],
|
||||
form,
|
||||
);
|
||||
|
||||
// Support both controlled and uncontrolled modes
|
||||
const [internalExpanded, setInternalExpanded] = useState(false);
|
||||
const isControlled = isExpanded !== undefined;
|
||||
const expanded = isControlled ? isExpanded : internalExpanded;
|
||||
|
||||
const handleCollapseChange = useCallback(
|
||||
(keys: string | string[]): void => {
|
||||
const newExpanded = Array.isArray(keys) ? keys.length > 0 : !!keys;
|
||||
if (isControlled && onExpandChange) {
|
||||
onExpandChange(newExpanded);
|
||||
} else {
|
||||
setInternalExpanded(newExpanded);
|
||||
}
|
||||
},
|
||||
[isControlled, onExpandChange],
|
||||
);
|
||||
|
||||
const collapseActiveKey = expanded ? ['role-mapping'] : [];
|
||||
|
||||
return (
|
||||
<div className="role-mapping-section">
|
||||
<Collapse
|
||||
bordered={false}
|
||||
activeKey={collapseActiveKey}
|
||||
onChange={handleCollapseChange}
|
||||
className="role-mapping-section__collapse"
|
||||
expandIcon={(): null => null}
|
||||
>
|
||||
<Collapse.Panel
|
||||
key="role-mapping"
|
||||
header={
|
||||
<div className="role-mapping-section__collapse-header">
|
||||
{!expanded ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
|
||||
<div className="role-mapping-section__collapse-header-text">
|
||||
<h4 className="role-mapping-section__section-title">
|
||||
Role Mapping (Advanced)
|
||||
</h4>
|
||||
<p className="role-mapping-section__section-description">
|
||||
Configure how user roles are determined from your Identity Provider.
|
||||
You can either use a direct role attribute or map IDP groups to SigNoz
|
||||
roles.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="role-mapping-section__content">
|
||||
{/* Default Role */}
|
||||
<div className="role-mapping-section__field-group">
|
||||
<label className="role-mapping-section__label" htmlFor="default-role">
|
||||
Default Role
|
||||
<Tooltip title='The default role assigned to new SSO users if no other role mapping applies. Default: "VIEWER"'>
|
||||
<CircleHelp size={14} className="role-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'defaultRole']}
|
||||
className="role-mapping-section__form-item"
|
||||
initialValue="VIEWER"
|
||||
>
|
||||
<Select
|
||||
id="default-role"
|
||||
options={ROLE_OPTIONS}
|
||||
className="role-mapping-section__select"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* Use Role Attribute */}
|
||||
<div className="role-mapping-section__checkbox-row">
|
||||
<Form.Item
|
||||
name={[...fieldNamePrefix, 'useRoleAttribute']}
|
||||
valuePropName="checked"
|
||||
noStyle
|
||||
>
|
||||
<Checkbox
|
||||
id="use-role-attribute"
|
||||
labelName="Use Role Attribute Directly"
|
||||
onCheckedChange={(checked: boolean): void => {
|
||||
form.setFieldValue([...fieldNamePrefix, 'useRoleAttribute'], checked);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Tooltip title="If enabled, the role claim/attribute from the IDP will be used directly instead of group mappings. The role value must match a SigNoz role (VIEWER, EDITOR, or ADMIN).">
|
||||
<CircleHelp size={14} className="role-mapping-section__label-icon" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* Group to Role Mappings - only show when useRoleAttribute is false */}
|
||||
{!useRoleAttribute && (
|
||||
<div className="role-mapping-section__group-mappings">
|
||||
<div className="role-mapping-section__group-header">
|
||||
<span className="role-mapping-section__group-title">
|
||||
Group to Role Mappings
|
||||
</span>
|
||||
<p className="role-mapping-section__group-description">
|
||||
Map IDP group names to SigNoz roles. If a user belongs to multiple
|
||||
groups, the highest privilege role will be assigned.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form.List name={[...fieldNamePrefix, 'groupMappingsList']}>
|
||||
{(fields, { add, remove }): JSX.Element => (
|
||||
<div className="role-mapping-section__items">
|
||||
{fields.map((field) => (
|
||||
<div key={field.key} className="role-mapping-section__row">
|
||||
<Form.Item
|
||||
name={[field.name, 'groupName']}
|
||||
className="role-mapping-section__field role-mapping-section__field--group"
|
||||
rules={[{ required: true, message: 'Group name is required' }]}
|
||||
>
|
||||
<Input placeholder="IDP Group Name" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={[field.name, 'role']}
|
||||
className="role-mapping-section__field role-mapping-section__field--role"
|
||||
rules={[{ required: true, message: 'Role is required' }]}
|
||||
initialValue="VIEWER"
|
||||
>
|
||||
<Select
|
||||
options={ROLE_OPTIONS}
|
||||
className="role-mapping-section__select"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
className="role-mapping-section__remove-btn"
|
||||
onClick={(): void => remove(field.name)}
|
||||
aria-label="Remove mapping"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button
|
||||
variant="dashed"
|
||||
onClick={(): void => add({ groupName: '', role: 'VIEWER' })}
|
||||
prefixIcon={<Plus size={14} />}
|
||||
className="role-mapping-section__add-btn"
|
||||
>
|
||||
Add Group Mapping
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RoleMappingSection;
|
||||
@@ -1,73 +1,45 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Switch } from '@signozhq/switch';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { useUpdateAuthDomain } from 'api/generated/services/authdomains';
|
||||
import {
|
||||
AuthtypesGettableAuthDomainDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useState } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
import put from 'api/v1/domains/id/put';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { ErrorV2Resp } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
interface ToggleProps {
|
||||
isDefaultChecked: boolean;
|
||||
record: AuthtypesGettableAuthDomainDTO;
|
||||
}
|
||||
import { GettableAuthDomain } from 'types/api/v1/domains/list';
|
||||
|
||||
function Toggle({ isDefaultChecked, record }: ToggleProps): JSX.Element {
|
||||
const [isChecked, setIsChecked] = useState<boolean>(isDefaultChecked);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
useEffect(() => {
|
||||
setIsChecked(isDefaultChecked);
|
||||
}, [isDefaultChecked]);
|
||||
const onChangeHandler = async (checked: boolean): Promise<void> => {
|
||||
setIsLoading(true);
|
||||
|
||||
const { mutate: updateAuthDomain, isLoading } = useUpdateAuthDomain<
|
||||
AxiosError<RenderErrorResponseDTO>
|
||||
>();
|
||||
|
||||
const onChangeHandler = (checked: boolean): void => {
|
||||
if (!record.id) {
|
||||
return;
|
||||
try {
|
||||
await put({
|
||||
id: record.id,
|
||||
config: {
|
||||
ssoEnabled: checked,
|
||||
ssoType: record.ssoType,
|
||||
googleAuthConfig: record.googleAuthConfig,
|
||||
oidcConfig: record.oidcConfig,
|
||||
samlConfig: record.samlConfig,
|
||||
},
|
||||
});
|
||||
setIsChecked(checked);
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
|
||||
updateAuthDomain(
|
||||
{
|
||||
pathParams: { id: record.id },
|
||||
data: {
|
||||
config: {
|
||||
ssoEnabled: checked,
|
||||
ssoType: record.ssoType,
|
||||
googleAuthConfig: record.googleAuthConfig,
|
||||
oidcConfig: record.oidcConfig,
|
||||
samlConfig: record.samlConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsChecked(checked);
|
||||
},
|
||||
onError: (error) => {
|
||||
try {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
showErrorModal(apiError as APIError);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
disabled={isLoading}
|
||||
checked={isChecked}
|
||||
onCheckedChange={onChangeHandler}
|
||||
/>
|
||||
<Switch loading={isLoading} checked={isChecked} onChange={onChangeHandler} />
|
||||
);
|
||||
}
|
||||
|
||||
interface ToggleProps {
|
||||
isDefaultChecked: boolean;
|
||||
record: GettableAuthDomain;
|
||||
}
|
||||
|
||||
export default Toggle;
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import AuthDomain from '../index';
|
||||
import {
|
||||
AUTH_DOMAINS_LIST_ENDPOINT,
|
||||
mockDomainsListResponse,
|
||||
mockEmptyDomainsResponse,
|
||||
mockErrorResponse,
|
||||
} from './mocks';
|
||||
|
||||
const successNotification = jest.fn();
|
||||
jest.mock('hooks/useNotifications', () => ({
|
||||
__esModule: true,
|
||||
useNotifications: jest.fn(() => ({
|
||||
notifications: {
|
||||
success: successNotification,
|
||||
error: jest.fn(),
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('AuthDomain', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
describe('List View', () => {
|
||||
it('renders page header and add button', async () => {
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(mockEmptyDomainsResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
expect(screen.getByText(/authenticated domains/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /add domain/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders list of auth domains successfully', async () => {
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(mockDomainsListResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('signoz.io')).toBeInTheDocument();
|
||||
expect(screen.getByText('example.com')).toBeInTheDocument();
|
||||
expect(screen.getByText('corp.io')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders empty state when no domains exist', async () => {
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(mockEmptyDomainsResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/no data/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays error content when API fails', async () => {
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json(mockErrorResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/failed to perform operation/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add Domain', () => {
|
||||
it('opens create modal when Add Domain button is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(mockEmptyDomainsResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
const addButton = await screen.findByRole('button', { name: /add domain/i });
|
||||
await user.click(addButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/configure authentication method/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configure Domain', () => {
|
||||
it('opens edit modal when configure action is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(mockDomainsListResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<AuthDomain />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('signoz.io')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const configureLinks = await screen.findAllByText(/configure google auth/i);
|
||||
await user.click(configureLinks[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/edit google authentication/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,354 +0,0 @@
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import CreateEdit from '../CreateEdit/CreateEdit';
|
||||
import {
|
||||
mockDomainWithRoleMapping,
|
||||
mockGoogleAuthDomain,
|
||||
mockGoogleAuthWithWorkspaceGroups,
|
||||
mockOidcAuthDomain,
|
||||
mockOidcWithClaimMapping,
|
||||
mockSamlAuthDomain,
|
||||
mockSamlWithAttributeMapping,
|
||||
} from './mocks';
|
||||
|
||||
const mockOnClose = jest.fn();
|
||||
|
||||
describe('CreateEdit Modal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Provider Selection (Create Mode)', () => {
|
||||
it('renders provider selection when creating new domain', () => {
|
||||
render(<CreateEdit isCreate onClose={mockOnClose} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(/configure authentication method/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(/google apps authentication/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/saml authentication/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/oidc authentication/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('returns to provider selection when back button is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<CreateEdit isCreate onClose={mockOnClose} />);
|
||||
|
||||
const configureButtons = await screen.findAllByRole('button', {
|
||||
name: /configure/i,
|
||||
});
|
||||
await user.click(configureButtons[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/edit google authentication/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const backButton = screen.getByRole('button', { name: /back/i });
|
||||
await user.click(backButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(/configure authentication method/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit Mode', () => {
|
||||
it('shows provider form directly when editing existing domain', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/edit google authentication/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText(/configure authentication method/i),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('pre-fills form with existing domain values', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByDisplayValue('signoz.io')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('test-client-id')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables domain field when editing', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const domainInput = screen.getByDisplayValue('signoz.io');
|
||||
expect(domainInput).toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows cancel button instead of back when editing', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /back/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form Validation', () => {
|
||||
it('shows validation error when submitting without required fields', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<CreateEdit isCreate onClose={mockOnClose} />);
|
||||
|
||||
const configureButtons = await screen.findAllByRole('button', {
|
||||
name: /configure/i,
|
||||
});
|
||||
await user.click(configureButtons[0]);
|
||||
|
||||
const saveButton = await screen.findByRole('button', {
|
||||
name: /save changes/i,
|
||||
});
|
||||
await user.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/domain is required/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Google Auth Provider', () => {
|
||||
it('shows Google Auth form fields', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<CreateEdit isCreate onClose={mockOnClose} />);
|
||||
|
||||
const configureButtons = await screen.findAllByRole('button', {
|
||||
name: /configure/i,
|
||||
});
|
||||
await user.click(configureButtons[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/edit google authentication/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/domain/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/client id/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/client secret/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/skip email verification/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows workspace groups section when expanded', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthWithWorkspaceGroups}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const workspaceHeader = screen.getByText(/google workspace groups/i);
|
||||
await user.click(workspaceHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/fetch groups/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/service account json/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SAML Provider', () => {
|
||||
it('shows SAML-specific fields when editing SAML domain', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockSamlAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/edit saml authentication/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByDisplayValue('https://idp.example.com/sso'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('urn:example:idp')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows attribute mapping section for SAML', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockSamlWithAttributeMapping}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText(/attribute mapping \(advanced\)/i),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const attributeHeader = screen.getByText(/attribute mapping \(advanced\)/i);
|
||||
await user.click(attributeHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/name attribute/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/groups attribute/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/role attribute/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OIDC Provider', () => {
|
||||
it('shows OIDC-specific fields when editing OIDC domain', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockOidcAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/edit oidc authentication/i)).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('https://oidc.corp.io')).toBeInTheDocument();
|
||||
expect(screen.getByDisplayValue('oidc-client-id')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows claim mapping section for OIDC', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockOidcWithClaimMapping}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/claim mapping \(advanced\)/i)).toBeInTheDocument();
|
||||
|
||||
const claimHeader = screen.getByText(/claim mapping \(advanced\)/i);
|
||||
await user.click(claimHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/email claim/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/name claim/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/groups claim/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/role claim/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows OIDC options checkboxes', () => {
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockOidcAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/skip email verification/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/get user info/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Role Mapping', () => {
|
||||
it('shows role mapping section in provider forms', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(<CreateEdit isCreate onClose={mockOnClose} />);
|
||||
|
||||
const configureButtons = await screen.findAllByRole('button', {
|
||||
name: /configure/i,
|
||||
});
|
||||
await user.click(configureButtons[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/role mapping \(advanced\)/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('expands role mapping section to show default role selector', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockDomainWithRoleMapping}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const roleMappingHeader = screen.getByText(/role mapping \(advanced\)/i);
|
||||
await user.click(roleMappingHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/default role/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/use role attribute directly/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows group mappings section when useRoleAttribute is false', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockDomainWithRoleMapping}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const roleMappingHeader = screen.getByText(/role mapping \(advanced\)/i);
|
||||
await user.click(roleMappingHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/group to role mappings/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('button', { name: /add group mapping/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Modal Actions', () => {
|
||||
it('calls onClose when cancel button is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<CreateEdit
|
||||
isCreate={false}
|
||||
record={mockGoogleAuthDomain}
|
||||
onClose={mockOnClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,115 +0,0 @@
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import Toggle from '../Toggle';
|
||||
import {
|
||||
AUTH_DOMAINS_UPDATE_ENDPOINT,
|
||||
mockErrorResponse,
|
||||
mockGoogleAuthDomain,
|
||||
mockUpdateSuccessResponse,
|
||||
} from './mocks';
|
||||
|
||||
describe('Toggle', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
});
|
||||
|
||||
it('renders switch with correct initial state', () => {
|
||||
render(<Toggle isDefaultChecked={true} record={mockGoogleAuthDomain} />);
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
expect(switchElement).toBeChecked();
|
||||
});
|
||||
|
||||
it('renders unchecked switch when SSO is disabled', () => {
|
||||
render(
|
||||
<Toggle
|
||||
isDefaultChecked={false}
|
||||
record={{ ...mockGoogleAuthDomain, ssoEnabled: false }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
expect(switchElement).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('calls update API when toggle is clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockUpdateAPI = jest.fn();
|
||||
|
||||
server.use(
|
||||
rest.put(AUTH_DOMAINS_UPDATE_ENDPOINT, async (req, res, ctx) => {
|
||||
const body = await req.json();
|
||||
mockUpdateAPI(body);
|
||||
return res(ctx.status(200), ctx.json(mockUpdateSuccessResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(<Toggle isDefaultChecked={true} record={mockGoogleAuthDomain} />);
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
await user.click(switchElement);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(switchElement).not.toBeChecked();
|
||||
});
|
||||
|
||||
expect(mockUpdateAPI).toHaveBeenCalledTimes(1);
|
||||
expect(mockUpdateAPI).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
ssoEnabled: false,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('shows error modal when update fails', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
server.use(
|
||||
rest.put(AUTH_DOMAINS_UPDATE_ENDPOINT, (_, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json(mockErrorResponse)),
|
||||
),
|
||||
);
|
||||
|
||||
render(<Toggle isDefaultChecked={true} record={mockGoogleAuthDomain} />);
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
await user.click(switchElement);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/failed to perform operation/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call API when record has no id', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
let apiCalled = false;
|
||||
|
||||
server.use(
|
||||
rest.put(AUTH_DOMAINS_UPDATE_ENDPOINT, (_, res, ctx) => {
|
||||
apiCalled = true;
|
||||
return res(ctx.status(200), ctx.json(mockUpdateSuccessResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<Toggle
|
||||
isDefaultChecked={true}
|
||||
record={{ ...mockGoogleAuthDomain, id: undefined }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
await user.click(switchElement);
|
||||
|
||||
// Wait a bit to ensure no API call was made
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
expect(apiCalled).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,220 +0,0 @@
|
||||
import { AuthtypesGettableAuthDomainDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
// API Endpoints
|
||||
export const AUTH_DOMAINS_LIST_ENDPOINT = '*/api/v1/domains';
|
||||
export const AUTH_DOMAINS_CREATE_ENDPOINT = '*/api/v1/domains';
|
||||
export const AUTH_DOMAINS_UPDATE_ENDPOINT = '*/api/v1/domains/:id';
|
||||
export const AUTH_DOMAINS_DELETE_ENDPOINT = '*/api/v1/domains/:id';
|
||||
|
||||
// Mock Auth Domain with Google Auth
|
||||
export const mockGoogleAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-1',
|
||||
name: 'signoz.io',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'google_auth',
|
||||
googleAuthConfig: {
|
||||
clientId: 'test-client-id',
|
||||
clientSecret: 'test-client-secret',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-1',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock Auth Domain with SAML
|
||||
export const mockSamlAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-2',
|
||||
name: 'example.com',
|
||||
ssoEnabled: false,
|
||||
ssoType: 'saml',
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.example.com/sso',
|
||||
samlEntity: 'urn:example:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-2',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock Auth Domain with OIDC
|
||||
export const mockOidcAuthDomain: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-3',
|
||||
name: 'corp.io',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'oidc',
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.corp.io',
|
||||
clientId: 'oidc-client-id',
|
||||
clientSecret: 'oidc-client-secret',
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-3',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock Auth Domain with Role Mapping
|
||||
export const mockDomainWithRoleMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-4',
|
||||
name: 'enterprise.com',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'saml',
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.enterprise.com/sso',
|
||||
samlEntity: 'urn:enterprise:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'EDITOR',
|
||||
useRoleAttribute: false,
|
||||
groupMappings: {
|
||||
'admin-group': 'ADMIN',
|
||||
'dev-team': 'EDITOR',
|
||||
viewers: 'VIEWER',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-4',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock Auth Domain with useRoleAttribute enabled
|
||||
export const mockDomainWithDirectRoleAttribute: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-5',
|
||||
name: 'direct-role.com',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'oidc',
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.direct-role.com',
|
||||
clientId: 'direct-role-client-id',
|
||||
clientSecret: 'direct-role-client-secret',
|
||||
},
|
||||
roleMapping: {
|
||||
defaultRole: 'VIEWER',
|
||||
useRoleAttribute: true,
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-5',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock OIDC domain with claim mapping
|
||||
export const mockOidcWithClaimMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-6',
|
||||
name: 'oidc-claims.com',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'oidc',
|
||||
oidcConfig: {
|
||||
issuer: 'https://oidc.claims.com',
|
||||
issuerAlias: 'https://alias.claims.com',
|
||||
clientId: 'claims-client-id',
|
||||
clientSecret: 'claims-client-secret',
|
||||
insecureSkipEmailVerified: true,
|
||||
getUserInfo: true,
|
||||
claimMapping: {
|
||||
email: 'user_email',
|
||||
name: 'display_name',
|
||||
groups: 'user_groups',
|
||||
role: 'user_role',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-6',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock SAML domain with attribute mapping
|
||||
export const mockSamlWithAttributeMapping: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-7',
|
||||
name: 'saml-attrs.com',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'saml',
|
||||
samlConfig: {
|
||||
samlIdp: 'https://idp.saml-attrs.com/sso',
|
||||
samlEntity: 'urn:saml-attrs:idp',
|
||||
samlCert: 'MOCK_CERTIFICATE_ATTRS',
|
||||
insecureSkipAuthNRequestsSigned: true,
|
||||
attributeMapping: {
|
||||
name: 'user_display_name',
|
||||
groups: 'member_of',
|
||||
role: 'signoz_role',
|
||||
},
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-7',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock Google Auth with workspace groups
|
||||
export const mockGoogleAuthWithWorkspaceGroups: AuthtypesGettableAuthDomainDTO = {
|
||||
id: 'domain-8',
|
||||
name: 'google-groups.com',
|
||||
ssoEnabled: true,
|
||||
ssoType: 'google_auth',
|
||||
googleAuthConfig: {
|
||||
clientId: 'google-groups-client-id',
|
||||
clientSecret: 'google-groups-client-secret',
|
||||
insecureSkipEmailVerified: false,
|
||||
fetchGroups: true,
|
||||
serviceAccountJson: '{"type": "service_account"}',
|
||||
domainToAdminEmail: {
|
||||
'google-groups.com': 'admin@google-groups.com',
|
||||
},
|
||||
fetchTransitiveGroupMembership: true,
|
||||
allowedGroups: ['allowed-group-1', 'allowed-group-2'],
|
||||
},
|
||||
authNProviderInfo: {
|
||||
relayStatePath: 'api/v1/sso/relay/domain-8',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock empty list response
|
||||
export const mockEmptyDomainsResponse = {
|
||||
status: 'success',
|
||||
data: [],
|
||||
};
|
||||
|
||||
// Mock list response with domains
|
||||
export const mockDomainsListResponse = {
|
||||
status: 'success',
|
||||
data: [mockGoogleAuthDomain, mockSamlAuthDomain, mockOidcAuthDomain],
|
||||
};
|
||||
|
||||
// Mock single domain list response
|
||||
export const mockSingleDomainResponse = {
|
||||
status: 'success',
|
||||
data: [mockGoogleAuthDomain],
|
||||
};
|
||||
|
||||
// Mock success responses
|
||||
export const mockCreateSuccessResponse = {
|
||||
status: 'success',
|
||||
data: mockGoogleAuthDomain,
|
||||
};
|
||||
|
||||
export const mockUpdateSuccessResponse = {
|
||||
status: 'success',
|
||||
data: { ...mockGoogleAuthDomain, ssoEnabled: false },
|
||||
};
|
||||
|
||||
export const mockDeleteSuccessResponse = {
|
||||
status: 'success',
|
||||
data: 'Domain deleted successfully',
|
||||
};
|
||||
|
||||
// Mock error responses
|
||||
export const mockErrorResponse = {
|
||||
error: {
|
||||
code: 'internal_error',
|
||||
message: 'Failed to perform operation',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const mockValidationErrorResponse = {
|
||||
error: {
|
||||
code: 'invalid_input',
|
||||
message: 'Domain name is required',
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
@@ -1,186 +1,147 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Table } from 'antd';
|
||||
import { Button, Table, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import {
|
||||
useDeleteAuthDomain,
|
||||
useListAuthDomains,
|
||||
} from 'api/generated/services/authdomains';
|
||||
import {
|
||||
AuthtypesGettableAuthDomainDTO,
|
||||
RenderErrorResponseDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import deleteDomain from 'api/v1/domains/id/delete';
|
||||
import listAllDomain from 'api/v1/domains/list';
|
||||
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import CopyToClipboard from 'periscope/components/CopyToClipboard';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { ErrorV2Resp } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { GettableAuthDomain, SSOType } from 'types/api/v1/domains/list';
|
||||
|
||||
import CreateEdit from './CreateEdit/CreateEdit';
|
||||
import Toggle from './Toggle';
|
||||
|
||||
import './AuthDomain.styles.scss';
|
||||
|
||||
export const SSOType = new Map<string, string>([
|
||||
['google_auth', 'Google Auth'],
|
||||
['saml', 'SAML'],
|
||||
['email_password', 'Email Password'],
|
||||
['oidc', 'OIDC'],
|
||||
]);
|
||||
const columns: ColumnsType<GettableAuthDomain> = [
|
||||
{
|
||||
title: 'Domain',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
render: (val): JSX.Element => <Typography.Text>{val}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: 'Enforce SSO',
|
||||
dataIndex: 'ssoEnabled',
|
||||
key: 'ssoEnabled',
|
||||
width: 80,
|
||||
render: (value: boolean, record: GettableAuthDomain): JSX.Element => (
|
||||
<Toggle isDefaultChecked={value} record={record} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'IDP Initiated SSO URL',
|
||||
dataIndex: 'relayState',
|
||||
key: 'relayState',
|
||||
width: 80,
|
||||
render: (_, record: GettableAuthDomain): JSX.Element => {
|
||||
const relayPath = record.authNProviderInfo.relayStatePath;
|
||||
if (!relayPath) {
|
||||
return (
|
||||
<Typography.Text style={{ paddingLeft: '6px' }}>N/A</Typography.Text>
|
||||
);
|
||||
}
|
||||
|
||||
const href = `${window.location.origin}/${relayPath}`;
|
||||
return <CopyToClipboard textToCopy={href} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
render: (_, record: GettableAuthDomain): JSX.Element => (
|
||||
<section className="auth-domain-list-column-action">
|
||||
<Typography.Link data-column-action="configure">
|
||||
Configure {SSOType.get(record.ssoType)}
|
||||
</Typography.Link>
|
||||
<Typography.Link type="danger" data-column-action="delete">
|
||||
Delete
|
||||
</Typography.Link>
|
||||
</section>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
async function deleteDomainById(
|
||||
id: string,
|
||||
showErrorModal: (error: APIError) => void,
|
||||
refetchAuthDomainListResponse: () => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await deleteDomain(id);
|
||||
refetchAuthDomainListResponse();
|
||||
} catch (error) {
|
||||
showErrorModal(error as APIError);
|
||||
}
|
||||
}
|
||||
|
||||
function AuthDomain(): JSX.Element {
|
||||
const [record, setRecord] = useState<AuthtypesGettableAuthDomainDTO>();
|
||||
const [record, setRecord] = useState<GettableAuthDomain>();
|
||||
const [addDomain, setAddDomain] = useState<boolean>(false);
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const {
|
||||
data: authDomainListResponse,
|
||||
isLoading: isLoadingAuthDomainListResponse,
|
||||
isFetching: isFetchingAuthDomainListResponse,
|
||||
error: errorFetchingAuthDomainListResponse,
|
||||
refetch: refetchAuthDomainListResponse,
|
||||
} = useListAuthDomains();
|
||||
|
||||
const { mutate: deleteAuthDomain } = useDeleteAuthDomain<
|
||||
AxiosError<RenderErrorResponseDTO>
|
||||
>();
|
||||
|
||||
const handleDeleteDomain = useCallback(
|
||||
(id: string): void => {
|
||||
deleteAuthDomain(
|
||||
{ pathParams: { id } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'Domain deleted successfully',
|
||||
});
|
||||
refetchAuthDomainListResponse();
|
||||
},
|
||||
onError: (error) => {
|
||||
try {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
showErrorModal(apiError as APIError);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
[
|
||||
deleteAuthDomain,
|
||||
notifications,
|
||||
refetchAuthDomainListResponse,
|
||||
showErrorModal,
|
||||
],
|
||||
);
|
||||
|
||||
const formattedError = useMemo(() => {
|
||||
if (!errorFetchingAuthDomainListResponse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
ErrorResponseHandlerV2(
|
||||
errorFetchingAuthDomainListResponse as AxiosError<ErrorV2Resp>,
|
||||
);
|
||||
} catch (error) {
|
||||
return error as APIError;
|
||||
}
|
||||
}, [errorFetchingAuthDomainListResponse]);
|
||||
|
||||
const columns: ColumnsType<AuthtypesGettableAuthDomainDTO> = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: 'Domain',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
render: (val): JSX.Element => <span>{val}</span>,
|
||||
},
|
||||
{
|
||||
title: 'Enforce SSO',
|
||||
dataIndex: 'ssoEnabled',
|
||||
key: 'ssoEnabled',
|
||||
width: 80,
|
||||
render: (
|
||||
value: boolean,
|
||||
record: AuthtypesGettableAuthDomainDTO,
|
||||
): JSX.Element => <Toggle isDefaultChecked={value} record={record} />,
|
||||
},
|
||||
{
|
||||
title: 'IDP Initiated SSO URL',
|
||||
dataIndex: 'relayState',
|
||||
key: 'relayState',
|
||||
width: 80,
|
||||
render: (_, record: AuthtypesGettableAuthDomainDTO): JSX.Element => {
|
||||
const relayPath = record.authNProviderInfo?.relayStatePath;
|
||||
if (!relayPath) {
|
||||
return <span className="auth-domain-list-na">N/A</span>;
|
||||
}
|
||||
|
||||
const href = `${window.location.origin}/${relayPath}`;
|
||||
return <CopyToClipboard textToCopy={href} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
render: (_, record: AuthtypesGettableAuthDomainDTO): JSX.Element => (
|
||||
<section className="auth-domain-list-column-action">
|
||||
<Button
|
||||
className="auth-domain-list-action-link"
|
||||
onClick={(): void => setRecord(record)}
|
||||
variant="link"
|
||||
>
|
||||
Configure {SSOType.get(record.ssoType || '')}
|
||||
</Button>
|
||||
<Button
|
||||
className="auth-domain-list-action-link delete"
|
||||
onClick={(): void => {
|
||||
if (record.id) {
|
||||
handleDeleteDomain(record.id);
|
||||
}
|
||||
}}
|
||||
variant="link"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</section>
|
||||
),
|
||||
},
|
||||
],
|
||||
[handleDeleteDomain],
|
||||
);
|
||||
} = useQuery({
|
||||
queryFn: listAllDomain,
|
||||
queryKey: ['/api/v1/domains', 'list'],
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="auth-domain">
|
||||
<section className="auth-domain-header">
|
||||
<h3 className="auth-domain-title">Authenticated Domains</h3>
|
||||
<Typography.Title level={3}>Authenticated Domains</Typography.Title>
|
||||
<Button
|
||||
prefixIcon={<PlusOutlined />}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={(): void => {
|
||||
setAddDomain(true);
|
||||
}}
|
||||
variant="solid"
|
||||
size="sm"
|
||||
color="primary"
|
||||
className="button"
|
||||
>
|
||||
Add Domain
|
||||
</Button>
|
||||
</section>
|
||||
{formattedError && <ErrorContent error={formattedError} />}
|
||||
{!errorFetchingAuthDomainListResponse && (
|
||||
{(errorFetchingAuthDomainListResponse as APIError) && (
|
||||
<ErrorContent error={errorFetchingAuthDomainListResponse as APIError} />
|
||||
)}
|
||||
{!(errorFetchingAuthDomainListResponse as APIError) && (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={authDomainListResponse?.data?.data}
|
||||
onRow={undefined}
|
||||
dataSource={authDomainListResponse?.data}
|
||||
onRow={(record): any => ({
|
||||
onClick: (
|
||||
event: React.SyntheticEvent<HTMLLinkElement, MouseEvent>,
|
||||
): void => {
|
||||
const target = event.target as HTMLLinkElement;
|
||||
const { columnAction } = target.dataset;
|
||||
switch (columnAction) {
|
||||
case 'configure':
|
||||
setRecord(record);
|
||||
|
||||
break;
|
||||
case 'delete':
|
||||
deleteDomainById(
|
||||
record.id,
|
||||
showErrorModal,
|
||||
refetchAuthDomainListResponse,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
console.error('Unknown action:', columnAction);
|
||||
}
|
||||
},
|
||||
})}
|
||||
loading={
|
||||
isLoadingAuthDomainListResponse || isFetchingAuthDomainListResponse
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user