mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-23 06:04:25 +08:00
feat: show environments in a separate dropdown (#4717)
* feat: show environments in a separate dropdown
This commit is contained in:
parent
dbd4363ff8
commit
a30b75a2a8
@ -0,0 +1,20 @@
|
||||
.resourceAttributesFilter-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: stretch;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.resource-attributes-selector {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.environment-selector {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
@ -1,10 +1,17 @@
|
||||
import './ResourceAttributesFilter.styles.scss';
|
||||
|
||||
import { CloseCircleFilled } from '@ant-design/icons';
|
||||
import { Button, Select, Spin } from 'antd';
|
||||
import useResourceAttribute, {
|
||||
isResourceEmpty,
|
||||
} from 'hooks/useResourceAttribute';
|
||||
import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import {
|
||||
convertMetricKeyToTrace,
|
||||
getEnvironmentTagKeys,
|
||||
getEnvironmentTagValues,
|
||||
} from 'hooks/useResourceAttribute/utils';
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { SelectOption } from 'types/common/select';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@ -22,60 +29,129 @@ function ResourceAttributesFilter({
|
||||
handleClearAll,
|
||||
handleFocus,
|
||||
handleChange,
|
||||
handleEnvironmentChange,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
loading,
|
||||
} = useResourceAttribute();
|
||||
|
||||
const isEmpty = useMemo(
|
||||
() => isResourceEmpty(queries, staging, selectedQuery),
|
||||
[queries, selectedQuery, staging],
|
||||
const [environments, setEnvironments] = useState<
|
||||
SelectOption<string, string>[]
|
||||
>([]);
|
||||
|
||||
const [selectedEnvironments, setSelectedEnvironments] = useState<string[]>([]);
|
||||
|
||||
const queriesExcludingEnvironment = useMemo(
|
||||
() =>
|
||||
queries.filter(
|
||||
(query) => query.tagKey !== 'resource_deployment_environment',
|
||||
),
|
||||
[queries],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<div>
|
||||
{queries.map((query) => (
|
||||
<QueryChip key={query.id} queryData={query} onClose={handleClose} />
|
||||
))}
|
||||
{staging.map((query, idx) => (
|
||||
<QueryChipItem key={uuid()}>
|
||||
{idx === 0 ? convertMetricKeyToTrace(query) : query}
|
||||
</QueryChipItem>
|
||||
))}
|
||||
</div>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
placeholder={!isEmpty && 'Search and Filter based on resource attributes.'}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
value={selectedQuery as never}
|
||||
style={{ flex: 1 }}
|
||||
options={optionsData.options}
|
||||
mode={optionsData?.mode}
|
||||
showArrow={!!suffixIcon}
|
||||
onClick={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onClear={handleClearAll}
|
||||
suffixIcon={suffixIcon}
|
||||
notFoundContent={
|
||||
loading ? (
|
||||
<span>
|
||||
<Spin size="small" /> Loading...
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
No resource attributes available to filter. Please refer docs to send
|
||||
attributes.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
const isEmpty = useMemo(
|
||||
() => isResourceEmpty(queriesExcludingEnvironment, staging, selectedQuery),
|
||||
[queriesExcludingEnvironment, selectedQuery, staging],
|
||||
);
|
||||
|
||||
{queries.length || staging.length || selectedQuery.length ? (
|
||||
<Button onClick={handleClearAll} icon={<CloseCircleFilled />} type="text" />
|
||||
) : null}
|
||||
</SearchContainer>
|
||||
useEffect(() => {
|
||||
const resourceDeploymentEnvironmentQuery = queries.filter(
|
||||
(query) => query.tagKey === 'resource_deployment_environment',
|
||||
);
|
||||
|
||||
if (resourceDeploymentEnvironmentQuery?.length > 0) {
|
||||
setSelectedEnvironments(resourceDeploymentEnvironmentQuery[0].tagValue);
|
||||
} else {
|
||||
setSelectedEnvironments([]);
|
||||
}
|
||||
}, [queries]);
|
||||
|
||||
useEffect(() => {
|
||||
getEnvironmentTagKeys().then((tagKeys) => {
|
||||
if (tagKeys && Array.isArray(tagKeys) && tagKeys.length > 0) {
|
||||
getEnvironmentTagValues().then((tagValues) => {
|
||||
setEnvironments(tagValues);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="resourceAttributesFilter-container">
|
||||
<div className="environment-selector">
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
key={selectedEnvironments.join('')}
|
||||
showSearch
|
||||
mode="multiple"
|
||||
value={selectedEnvironments}
|
||||
placeholder="Select Environment/s"
|
||||
data-testId="resource-environment-filter"
|
||||
style={{ minWidth: 200, height: 34 }}
|
||||
onChange={handleEnvironmentChange}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
{environments.map((opt) => (
|
||||
<Select.Option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="resource-attributes-selector">
|
||||
<SearchContainer>
|
||||
<div>
|
||||
{queriesExcludingEnvironment.map((query) => (
|
||||
<QueryChip key={query.id} queryData={query} onClose={handleClose} />
|
||||
))}
|
||||
{staging.map((query, idx) => (
|
||||
<QueryChipItem key={uuid()}>
|
||||
{idx === 0 ? convertMetricKeyToTrace(query) : query}
|
||||
</QueryChipItem>
|
||||
))}
|
||||
</div>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
placeholder={
|
||||
!isEmpty && 'Search and Filter based on resource attributes.'
|
||||
}
|
||||
onChange={handleChange}
|
||||
bordered={false}
|
||||
value={selectedQuery as never}
|
||||
style={{ flex: 1 }}
|
||||
options={optionsData.options}
|
||||
mode={optionsData?.mode}
|
||||
data-testId="resource-attributes-filter"
|
||||
showArrow={!!suffixIcon}
|
||||
onClick={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onClear={handleClearAll}
|
||||
suffixIcon={suffixIcon}
|
||||
notFoundContent={
|
||||
loading ? (
|
||||
<span>
|
||||
<Spin size="small" /> Loading...
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
No resource attributes available to filter. Please refer docs to send
|
||||
attributes.
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{queries.length || staging.length || selectedQuery.length ? (
|
||||
<Button
|
||||
onClick={handleClearAll}
|
||||
icon={<CloseCircleFilled />}
|
||||
type="text"
|
||||
/>
|
||||
) : null}
|
||||
</SearchContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,10 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
|
||||
<QueryChipContainer>
|
||||
<QueryChipItem>{convertMetricKeyToTrace(queryData.tagKey)}</QueryChipItem>
|
||||
<QueryChipItem>{queryData.operator}</QueryChipItem>
|
||||
<QueryChipItem closable onClose={onCloseHandler}>
|
||||
<QueryChipItem
|
||||
closable={queryData.tagKey !== 'resource_deployment_environment'}
|
||||
onClose={onCloseHandler}
|
||||
>
|
||||
{queryData.tagValue.join(', ')}
|
||||
</QueryChipItem>
|
||||
</QueryChipContainer>
|
||||
|
@ -7,9 +7,10 @@ export const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
padding: 0.2rem;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid #ccc5;
|
||||
padding: 0 0.2rem;
|
||||
border: 1px solid #454c58;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
`;
|
||||
|
||||
export const QueryChipContainer = styled.span`
|
||||
|
@ -52,6 +52,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
? `?resourceAttribute=${encode(JSON.stringify(queries))}`
|
||||
: '',
|
||||
});
|
||||
|
||||
setQueries(queries);
|
||||
},
|
||||
[pathname],
|
||||
@ -62,12 +63,14 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
onSelectTagKey: () => {
|
||||
handleLoading(true);
|
||||
GetTagKeys()
|
||||
.then((tagKeys) =>
|
||||
.then((tagKeys) => {
|
||||
const options = mappingWithRoutesAndKeys(pathname, tagKeys);
|
||||
|
||||
setOptionsData({
|
||||
options: mappingWithRoutesAndKeys(pathname, tagKeys),
|
||||
options,
|
||||
mode: undefined,
|
||||
}),
|
||||
)
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
handleLoading(false);
|
||||
});
|
||||
@ -96,6 +99,7 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
}
|
||||
|
||||
const generatedQuery = createQuery([...staging, selectedQuery]);
|
||||
|
||||
if (generatedQuery) {
|
||||
dispatchQueries([...queries, generatedQuery]);
|
||||
}
|
||||
@ -127,6 +131,29 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
[optionsData.mode, send],
|
||||
);
|
||||
|
||||
const handleEnvironmentChange = useCallback(
|
||||
(environments: string[]): void => {
|
||||
const staging = ['resource_deployment_environment', 'IN'];
|
||||
|
||||
const queriesCopy = queries.filter(
|
||||
(query) => query.tagKey !== 'resource_deployment_environment',
|
||||
);
|
||||
|
||||
if (environments && Array.isArray(environments) && environments.length > 0) {
|
||||
const generatedQuery = createQuery([...staging, environments]);
|
||||
|
||||
if (generatedQuery) {
|
||||
dispatchQueries([...queriesCopy, generatedQuery]);
|
||||
}
|
||||
} else {
|
||||
dispatchQueries([...queriesCopy]);
|
||||
}
|
||||
|
||||
send('RESET');
|
||||
},
|
||||
[dispatchQueries, queries, send],
|
||||
);
|
||||
|
||||
const handleClose = useCallback(
|
||||
(id: string): void => {
|
||||
dispatchQueries(queries.filter((queryData) => queryData.id !== id));
|
||||
@ -159,12 +186,14 @@ function ResourceProvider({ children }: Props): JSX.Element {
|
||||
handleFocus,
|
||||
loading,
|
||||
handleChange,
|
||||
handleEnvironmentChange,
|
||||
selectedQuery,
|
||||
optionsData,
|
||||
}),
|
||||
[
|
||||
handleBlur,
|
||||
handleChange,
|
||||
handleEnvironmentChange,
|
||||
handleClearAll,
|
||||
handleClose,
|
||||
handleFocus,
|
||||
|
@ -28,4 +28,5 @@ export interface IResourceAttributeProps {
|
||||
handleChange: (value: string) => void;
|
||||
selectedQuery: string[];
|
||||
optionsData: OptionsData;
|
||||
handleEnvironmentChange: (environments: string[]) => void;
|
||||
}
|
||||
|
@ -109,12 +109,43 @@ export const GetTagKeys = async (): Promise<IOption[]> => {
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data
|
||||
.filter((tagKey: string) => tagKey !== 'resource_deployment_environment')
|
||||
.map((tagKey: string) => ({
|
||||
label: convertMetricKeyToTrace(tagKey),
|
||||
value: tagKey,
|
||||
}));
|
||||
};
|
||||
|
||||
export const getEnvironmentTagKeys = async (): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagKeys({
|
||||
metricName: 'signoz_calls_total',
|
||||
match: 'resource_deployment_environment',
|
||||
});
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagKey: string) => ({
|
||||
label: convertMetricKeyToTrace(tagKey),
|
||||
value: tagKey,
|
||||
}));
|
||||
};
|
||||
|
||||
export const getEnvironmentTagValues = async (): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagValues({
|
||||
tagKey: 'resource_deployment_environment',
|
||||
metricName: 'signoz_calls_total',
|
||||
});
|
||||
|
||||
if (!payload || !payload?.data) {
|
||||
return [];
|
||||
}
|
||||
return payload.data.map((tagValue: string) => ({
|
||||
label: tagValue,
|
||||
value: tagValue,
|
||||
}));
|
||||
};
|
||||
|
||||
export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
|
||||
const { payload } = await getResourceAttributesTagValues({
|
||||
tagKey,
|
||||
@ -132,6 +163,23 @@ export const GetTagValues = async (tagKey: string): Promise<IOption[]> => {
|
||||
|
||||
export const createQuery = (
|
||||
selectedItems: Array<string | string[]> = [],
|
||||
): IResourceAttribute | null => {
|
||||
console.log('selectedItems', selectedItems);
|
||||
|
||||
if (selectedItems.length === 3) {
|
||||
return {
|
||||
id: uuid().slice(0, 8),
|
||||
tagKey: selectedItems[0] as string,
|
||||
operator: selectedItems[1] as string,
|
||||
tagValue: selectedItems[2] as string[],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const updateQuery = (
|
||||
queryKey: string,
|
||||
selectedItems: Array<string | string[]> = [],
|
||||
): IResourceAttribute | null => {
|
||||
if (selectedItems.length === 3) {
|
||||
return {
|
||||
|
@ -6,9 +6,11 @@ describe('Services', () => {
|
||||
test('Should render the component', () => {
|
||||
render(<Metrics />);
|
||||
|
||||
const inputBox = screen.getByRole('combobox');
|
||||
const inputBox = screen.getByTestId('resource-attributes-filter');
|
||||
expect(inputBox).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('resource-environment-filter')).toBeInTheDocument();
|
||||
|
||||
const application = screen.getByRole('columnheader', {
|
||||
name: /application search/i,
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user