mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
170 lines
4.5 KiB
TypeScript
170 lines
4.5 KiB
TypeScript
import {useCallback, useMemo} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import memoize from 'lodash/memoize';
|
|
|
|
import type {SearchConfig} from 'sentry/components/searchSyntax/parser';
|
|
import {
|
|
FilterType,
|
|
joinQuery,
|
|
parseSearch,
|
|
Token,
|
|
} from 'sentry/components/searchSyntax/parser';
|
|
import {treeTransformer} from 'sentry/components/searchSyntax/utils';
|
|
import type {SmartSearchBarProps} from 'sentry/components/smartSearchBar';
|
|
import SmartSearchBar from 'sentry/components/smartSearchBar';
|
|
import {t} from 'sentry/locale';
|
|
import type {MRI, TagCollection} from 'sentry/types';
|
|
import {SavedSearchType} from 'sentry/types';
|
|
import {getUseCaseFromMRI} from 'sentry/utils/metrics/mri';
|
|
import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags';
|
|
import useApi from 'sentry/utils/useApi';
|
|
import useOrganization from 'sentry/utils/useOrganization';
|
|
import usePageFilters from 'sentry/utils/usePageFilters';
|
|
|
|
interface MetricSearchBarProps extends Partial<SmartSearchBarProps> {
|
|
onChange: (value: string) => void;
|
|
disabled?: boolean;
|
|
mri?: MRI;
|
|
projectIds?: string[];
|
|
query?: string;
|
|
}
|
|
|
|
const EMPTY_ARRAY = [];
|
|
const EMPTY_SET = new Set<never>();
|
|
|
|
export function ensureQuotedTextFilters(
|
|
query: string,
|
|
configOverrides?: Partial<SearchConfig>
|
|
) {
|
|
const parsedSearch = parseSearch(query, configOverrides);
|
|
|
|
if (!parsedSearch) {
|
|
return query;
|
|
}
|
|
|
|
const newTree = treeTransformer({
|
|
tree: parsedSearch,
|
|
transform: token => {
|
|
if (token.type === Token.FILTER && token.filter === FilterType.TEXT) {
|
|
if (!token.value.quoted) {
|
|
return {
|
|
...token,
|
|
// joinQuery() does not access nested tokens, so we need to manipulate the text of the filter instead of it's value
|
|
text: `${token.key.text}:"${token.value.text}"`,
|
|
};
|
|
}
|
|
}
|
|
return token;
|
|
},
|
|
});
|
|
|
|
return joinQuery(newTree);
|
|
}
|
|
|
|
export function MetricSearchBar({
|
|
mri,
|
|
disabled,
|
|
onChange,
|
|
query,
|
|
projectIds,
|
|
...props
|
|
}: MetricSearchBarProps) {
|
|
const org = useOrganization();
|
|
const api = useApi();
|
|
const {selection} = usePageFilters();
|
|
const projectIdNumbers = useMemo(
|
|
() => projectIds?.map(id => parseInt(id, 10)),
|
|
[projectIds]
|
|
);
|
|
|
|
const {data: tags = EMPTY_ARRAY, isLoading} = useMetricsTags(mri, {
|
|
...selection,
|
|
projects: projectIdNumbers,
|
|
});
|
|
|
|
const supportedTags: TagCollection = useMemo(
|
|
() => tags.reduce((acc, tag) => ({...acc, [tag.key]: tag}), {}),
|
|
[tags]
|
|
);
|
|
|
|
const searchConfig = useMemo(
|
|
() => ({
|
|
booleanKeys: EMPTY_SET,
|
|
dateKeys: EMPTY_SET,
|
|
durationKeys: EMPTY_SET,
|
|
numericKeys: EMPTY_SET,
|
|
percentageKeys: EMPTY_SET,
|
|
sizeKeys: EMPTY_SET,
|
|
textOperatorKeys: EMPTY_SET,
|
|
supportedTags,
|
|
disallowFreeText: true,
|
|
}),
|
|
[supportedTags]
|
|
);
|
|
|
|
const fetchTagValues = useMemo(() => {
|
|
const fn = memoize((tagKey: string) => {
|
|
// clear response from cache after 10 seconds
|
|
setTimeout(() => {
|
|
fn.cache.delete(tagKey);
|
|
}, 10000);
|
|
return api.requestPromise(`/organizations/${org.slug}/metrics/tags/${tagKey}/`, {
|
|
query: {
|
|
metric: mri,
|
|
useCase: getUseCaseFromMRI(mri),
|
|
project: selection.projects,
|
|
},
|
|
});
|
|
});
|
|
return fn;
|
|
}, [api, mri, org.slug, selection.projects]);
|
|
|
|
const getTagValues = useCallback(
|
|
async (tag: any, search: string) => {
|
|
const tagsValues = await fetchTagValues(tag.key);
|
|
|
|
return tagsValues
|
|
.filter(
|
|
tv =>
|
|
tv.value !== '' &&
|
|
tv.value.toLocaleLowerCase().includes(search.toLocaleLowerCase())
|
|
)
|
|
.map(tv => tv.value);
|
|
},
|
|
[fetchTagValues]
|
|
);
|
|
|
|
const handleChange = useCallback(
|
|
(value: string, {validSearch} = {validSearch: true}) => {
|
|
if (!validSearch) {
|
|
return;
|
|
}
|
|
onChange(ensureQuotedTextFilters(value, searchConfig));
|
|
},
|
|
[onChange, searchConfig]
|
|
);
|
|
|
|
return (
|
|
<WideSearchBar
|
|
disabled={disabled}
|
|
maxMenuHeight={220}
|
|
organization={org}
|
|
onGetTagValues={getTagValues}
|
|
// don't highlight tags while loading as we don't know yet if they are supported
|
|
highlightUnsupportedTags={!isLoading}
|
|
onClose={handleChange}
|
|
onSearch={handleChange}
|
|
placeholder={t('Filter by tags')}
|
|
query={query}
|
|
savedSearchType={SavedSearchType.METRIC}
|
|
{...searchConfig}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
const WideSearchBar = styled(SmartSearchBar)`
|
|
width: 100%;
|
|
opacity: ${p => (p.disabled ? '0.6' : '1')};
|
|
`;
|