mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-13 03:51:33 +08:00
165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
import {
|
|
CloseCircleFilled,
|
|
ExclamationCircleOutlined,
|
|
} from '@ant-design/icons';
|
|
import { useMachine } from '@xstate/react';
|
|
import { Button, Input, message, Modal } from 'antd';
|
|
import { map } from 'lodash-es';
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useSelector } from 'react-redux';
|
|
import { AppState } from 'store/reducers';
|
|
import { Labels } from 'types/api/alerts/def';
|
|
import AppReducer from 'types/reducer/app';
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { ResourceAttributesFilterMachine } from './Labels.machine';
|
|
import QueryChip from './QueryChip';
|
|
import { QueryChipItem, SearchContainer } from './styles';
|
|
import { ILabelRecord } from './types';
|
|
import { createQuery, flattenLabels, prepareLabels } from './utils';
|
|
|
|
interface LabelSelectProps {
|
|
onSetLabels: (q: Labels) => void;
|
|
initialValues: Labels | undefined;
|
|
}
|
|
|
|
function LabelSelect({
|
|
onSetLabels,
|
|
initialValues,
|
|
}: LabelSelectProps): JSX.Element | null {
|
|
const { t } = useTranslation('alerts');
|
|
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
|
const [currentVal, setCurrentVal] = useState('');
|
|
const [staging, setStaging] = useState<string[]>([]);
|
|
const [queries, setQueries] = useState<ILabelRecord[]>(
|
|
initialValues ? flattenLabels(initialValues) : [],
|
|
);
|
|
|
|
const dispatchChanges = (updatedRecs: ILabelRecord[]): void => {
|
|
onSetLabels(prepareLabels(updatedRecs, initialValues));
|
|
setQueries(updatedRecs);
|
|
};
|
|
|
|
const [state, send] = useMachine(ResourceAttributesFilterMachine, {
|
|
actions: {
|
|
onSelectLabelKey: () => {},
|
|
onSelectLabelValue: () => {
|
|
if (currentVal !== '') {
|
|
setStaging((prevState) => [...prevState, currentVal]);
|
|
} else {
|
|
return;
|
|
}
|
|
setCurrentVal('');
|
|
},
|
|
onValidateQuery: (): void => {
|
|
if (currentVal === '') {
|
|
return;
|
|
}
|
|
|
|
const generatedQuery = createQuery([...staging, currentVal]);
|
|
|
|
if (generatedQuery) {
|
|
dispatchChanges([...queries, generatedQuery]);
|
|
setStaging([]);
|
|
setCurrentVal('');
|
|
send('RESET');
|
|
}
|
|
},
|
|
},
|
|
});
|
|
|
|
const handleFocus = (): void => {
|
|
if (state.value === 'Idle') {
|
|
send('NEXT');
|
|
}
|
|
};
|
|
|
|
const handleBlur = useCallback((): void => {
|
|
if (staging.length === 1 && staging[0] !== undefined) {
|
|
send('onBlur');
|
|
}
|
|
}, [send, staging]);
|
|
|
|
useEffect(() => {
|
|
handleBlur();
|
|
}, [handleBlur]);
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
setCurrentVal(e.target?.value);
|
|
};
|
|
|
|
const handleClose = (key: string): void => {
|
|
dispatchChanges(queries.filter((queryData) => queryData.key !== key));
|
|
};
|
|
|
|
const handleClearAll = (): void => {
|
|
Modal.confirm({
|
|
title: 'Confirm',
|
|
icon: <ExclamationCircleOutlined />,
|
|
content: t('remove_label_confirm'),
|
|
onOk() {
|
|
send('RESET');
|
|
dispatchChanges([]);
|
|
setStaging([]);
|
|
message.success(t('remove_label_success'));
|
|
},
|
|
okText: t('button_yes'),
|
|
cancelText: t('button_no'),
|
|
});
|
|
};
|
|
const renderPlaceholder = useCallback((): string => {
|
|
if (state.value === 'LabelKey') return 'Enter a label key then press ENTER.';
|
|
if (state.value === 'LabelValue')
|
|
return `Enter a value for label key(${staging[0]}) then press ENTER.`;
|
|
return t('placeholder_label_key_pair');
|
|
}, [t, state, staging]);
|
|
return (
|
|
<SearchContainer isDarkMode={isDarkMode} disabled={false}>
|
|
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
|
|
{queries.length > 0 &&
|
|
map(
|
|
queries,
|
|
(query): JSX.Element => {
|
|
return (
|
|
<QueryChip key={query.key} queryData={query} onRemove={handleClose} />
|
|
);
|
|
},
|
|
)}
|
|
</div>
|
|
<div>
|
|
{map(staging, (item) => {
|
|
return <QueryChipItem key={uuid()}>{item}</QueryChipItem>;
|
|
})}
|
|
</div>
|
|
|
|
<div style={{ display: 'flex', width: '100%' }}>
|
|
<Input
|
|
placeholder={renderPlaceholder()}
|
|
onChange={handleChange}
|
|
onKeyUp={(e): void => {
|
|
if (e.key === 'Enter' || e.code === 'Enter') {
|
|
send('NEXT');
|
|
}
|
|
}}
|
|
bordered={false}
|
|
value={currentVal as never}
|
|
style={{ flex: 1 }}
|
|
onFocus={handleFocus}
|
|
onBlur={handleBlur}
|
|
/>
|
|
|
|
{queries.length || staging.length || currentVal ? (
|
|
<Button
|
|
onClick={handleClearAll}
|
|
icon={<CloseCircleFilled />}
|
|
type="text"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
</SearchContainer>
|
|
);
|
|
}
|
|
|
|
export default LabelSelect;
|