feat ui update search filter icons (#2010)

This commit is contained in:
Delirium 2024-03-28 15:46:31 +01:00 committed by GitHub
parent 2ac3d38078
commit 9c3493cf87
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 210 additions and 99 deletions

View file

@ -14,9 +14,6 @@
&:hover {
background-color: $active-blue;
color: $teal !important;
& svg {
fill: $teal !important;
}
}
}

View file

@ -1,29 +1,97 @@
import React from 'react';
import { Icon, Loader } from 'UI';
import { connect } from 'react-redux';
import { filtersMap } from 'Types/filter/newFilter';
import cn from 'classnames';
import stl from './FilterModal.module.css';
import { filtersMap, conditionalFiltersMap } from 'Types/filter/newFilter';
import {
AppWindow,
ArrowUpDown,
Chrome,
CircleAlert,
Clock2,
Code,
ContactRound,
Cpu,
Earth,
FileStack,
MapPin,
MemoryStick,
MonitorSmartphone,
Navigation,
Network,
OctagonAlert,
Pin,
Pointer,
RectangleEllipsis,
SquareMousePointer,
SquareUser,
Timer,
VenetianMask,
Workflow,
} from 'lucide-react';
import React from 'react';
import { connect } from 'react-redux';
import { Icon, Loader } from 'UI';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { FilterKey } from '../../../../types/filter/filterType';
import stl from './FilterModal.module.css';
const IconMap = {
[FilterKey.CLICK]: <Pointer size={18} />,
[FilterKey.LOCATION]: <Navigation size={18} />,
[FilterKey.INPUT]: <RectangleEllipsis size={18} />,
[FilterKey.CUSTOM]: <Code size={18} />,
[FilterKey.FETCH]: <ArrowUpDown size={18} />,
[FilterKey.GRAPHQL]: <Network size={18} />,
[FilterKey.STATEACTION]: <RectangleEllipsis size={18} />,
[FilterKey.ERROR]: <OctagonAlert size={18} />,
[FilterKey.ISSUE]: <CircleAlert size={18} />,
[FilterKey.FETCH_FAILED]: <Code size={18} />,
[FilterKey.DOM_COMPLETE]: <ArrowUpDown size={18} />,
[FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: <Network size={18} />,
[FilterKey.TTFB]: <Timer size={18} />,
[FilterKey.AVG_CPU_LOAD]: <Cpu size={18} />,
[FilterKey.AVG_MEMORY_USAGE]: <MemoryStick size={18} />,
[FilterKey.USERID]: <SquareUser size={18} />,
[FilterKey.USERANONYMOUSID]: <VenetianMask size={18} />,
[FilterKey.USER_CITY]: <Pin size={18} />,
[FilterKey.USER_STATE]: <MapPin size={18} />,
[FilterKey.USER_COUNTRY]: <Earth size={18} />,
[FilterKey.USER_DEVICE]: <Code size={18} />,
[FilterKey.USER_OS]: <AppWindow size={18} />,
[FilterKey.USER_BROWSER]: <Chrome size={18} />,
[FilterKey.PLATFORM]: <MonitorSmartphone size={18} />,
[FilterKey.REVID]: <FileStack size={18} />,
[FilterKey.REFERRER]: <Workflow size={18} />,
[FilterKey.DURATION]: <Clock2 size={18} />,
[FilterKey.TAGGED_ELEMENT]: <SquareMousePointer size={18} />,
[FilterKey.METADATA]: <ContactRound size={18} />,
};
function filterJson(
jsonObj: Record<string, any>,
excludeKeys: string[] = [],
allowedFilterKeys: string[] = []
): Record<string, any> {
return Object.fromEntries(
Object.entries(jsonObj).map(([key, value]) => {
const arr = value.filter((i: { key: string }) => {
if (excludeKeys.includes(i.key)) return false;
return !(allowedFilterKeys.length > 0 && !allowedFilterKeys.includes(i.key));
});
return [key, arr];
}).filter(([_, arr]) => arr.length > 0)
Object.entries(jsonObj)
.map(([key, value]) => {
const arr = value.filter((i: { key: string }) => {
if (excludeKeys.includes(i.key)) return false;
return !(
allowedFilterKeys.length > 0 && !allowedFilterKeys.includes(i.key)
);
});
return [key, arr];
})
.filter(([_, arr]) => arr.length > 0)
);
}
export const getMatchingEntries = (searchQuery: string, filters: Record<string, any>) => {
export const getMatchingEntries = (
searchQuery: string,
filters: Record<string, any>
) => {
const matchingCategories: string[] = [];
const matchingFilters: Record<string, any> = {};
const lowerCaseQuery = searchQuery.toLowerCase();
@ -31,7 +99,7 @@ export const getMatchingEntries = (searchQuery: string, filters: Record<string,
if (lowerCaseQuery.length === 0)
return {
matchingCategories: Object.keys(filters),
matchingFilters: filters
matchingFilters: filters,
};
Object.keys(filters).forEach((name) => {
@ -88,7 +156,11 @@ function FilterModal(props: Props) {
const { matchingCategories, matchingFilters } = getMatchingEntries(
searchQuery,
filterJson(isConditional ? conditionalFilters : filters, excludeFilterKeys, allowedFilterKeys)
filterJson(
isConditional ? conditionalFilters : filters,
excludeFilterKeys,
allowedFilterKeys
)
);
const isResultEmpty =
@ -96,31 +168,48 @@ function FilterModal(props: Props) {
matchingCategories.length === 0 &&
Object.keys(matchingFilters).length === 0;
const getNewIcon = (filter: Record<string, any>) => {
if (filter.icon.includes('metadata')) {
return IconMap[FilterKey.METADATA]
}
// @ts-ignore
if (IconMap[filter.key]) {
// @ts-ignore
return IconMap[filter.key]
}
else return <Icon name={filter.icon} size={16} />
}
return (
<div className={stl.wrapper} style={{ width: '480px', maxHeight: '380px', overflowY: 'auto' }}>
<div
className={stl.wrapper}
style={{ width: '480px', maxHeight: '380px', overflowY: 'auto' }}
>
<div
className={searchQuery && !isResultEmpty ? 'mb-6' : ''}
style={{ columns: matchingCategories.length > 1 ? 'auto 200px' : 1 }}
>
{matchingCategories.map((key) => {
return (
<div className='mb-6 flex flex-col gap-2 break-inside-avoid' key={key}>
<div className='uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm'>
<div
className="mb-6 flex flex-col gap-2 break-inside-avoid"
key={key}
>
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">
{key}
</div>
<div>
{matchingFilters[key] &&
matchingFilters[key].map((filter: any) => (
matchingFilters[key].map((filter: Record<string, any>) => (
<div
key={filter.label}
className={cn(
stl.optionItem,
'flex items-center py-2 cursor-pointer -mx-2 px-2'
'flex items-center py-2 cursor-pointer -mx-2 px-2 gap-2'
)}
onClick={() => onFilterClick({ ...filter, value: [''] })}
>
<Icon name={filter.icon} size='16' />
<span className='ml-2'>{filter.label}</span>
{getNewIcon(filter)}
<span>{filter.label}</span>
</div>
))}
</div>
@ -130,11 +219,14 @@ function FilterModal(props: Props) {
</div>
{showSearchList && (
<Loader loading={fetchingFilterSearchList}>
<div className='-mx-6 px-6'>
<div className="-mx-6 px-6">
{isResultEmpty && !fetchingFilterSearchList ? (
<div className='flex items-center flex-col'>
<div className="flex items-center flex-col">
<AnimatedSVG name={ICONS.NO_SEARCH_RESULTS} size={180} />
<div className='color-gray-medium font-medium px-3'> No Suggestions Found</div>
<div className="color-gray-medium font-medium px-3">
{' '}
No Suggestions Found
</div>
</div>
) : (
Object.keys(filterSearchList).map((key, index) => {
@ -142,7 +234,7 @@ function FilterModal(props: Props) {
const option = filtersMap[key];
return option ? (
<div key={index} className={cn('mb-3')}>
<div className='font-medium uppercase color-gray-medium mb-2'>
<div className="font-medium uppercase color-gray-medium mb-2">
{option.label}
</div>
<div>
@ -151,12 +243,14 @@ function FilterModal(props: Props) {
key={i}
className={cn(
stl.filterSearchItem,
'cursor-pointer px-3 py-1 flex items-center'
'cursor-pointer px-3 py-1 flex items-center gap-2'
)}
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
onClick={() =>
onFilterSearchClick({ type: key, value: f.value })
}
>
<Icon className='mr-2' name={option.icon} size='16' />
<div className='whitespace-nowrap text-ellipsis overflow-hidden'>
{getNewIcon(option)}
<div className="whitespace-nowrap text-ellipsis overflow-hidden">
{f.value}
</div>
</div>
@ -186,6 +280,6 @@ export default connect((state: any, props: any) => {
: state.getIn(['search', 'filterSearchList']),
fetchingFilterSearchList: props.isLive
? state.getIn(['liveSearch', 'fetchFilterSearch', 'loading'])
: state.getIn(['search', 'fetchFilterSearch', 'loading'])
: state.getIn(['search', 'fetchFilterSearch', 'loading']),
};
})(FilterModal);

View file

@ -2,7 +2,7 @@ export enum FilterCategory {
INTERACTIONS = 'Interactions',
GEAR = 'Gear',
RECORDING_ATTRIBUTES = 'Recording Attributes',
JAVASCRIPT = 'Javascript',
TECHNICAL = 'Technical',
USER = 'User Identification',
METADATA = 'Session & User Metadata',
PERFORMANCE = 'Performance',

View file

@ -8,6 +8,14 @@ import { capitalize } from 'App/utils';
const countryOptions = Object.keys(countries).map(i => ({ label: countries[i], value: i }));
const containsFilters = [{ key: 'contains', label: 'contains', text: 'contains', value: 'contains' }];
const filterOrder = {
[FilterCategory.INTERACTIONS]: 0,
[FilterCategory.TECHNICAL]: 1,
[FilterCategory.PERFORMANCE]: 2,
[FilterCategory.USER]: 3,
[FilterCategory.GEAR]: 4,
}
export const filters = [
{
key: FilterKey.CLICK,
@ -44,7 +52,7 @@ export const filters = [
{
key: FilterKey.CUSTOM,
type: FilterType.MULTIPLE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'Custom Events',
placeholder: 'Enter event key',
operator: 'is',
@ -56,7 +64,7 @@ export const filters = [
{
key: FilterKey.FETCH,
type: FilterType.SUB_FILTERS,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
operator: 'is',
label: 'Network Request',
filters: [
@ -126,7 +134,7 @@ export const filters = [
{
key: FilterKey.GRAPHQL,
type: FilterType.SUB_FILTERS,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'GraphQL',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
@ -175,7 +183,7 @@ export const filters = [
{
key: FilterKey.STATEACTION,
type: FilterType.MULTIPLE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'State Action',
placeholder: 'E.g. 12',
operator: 'is',
@ -186,7 +194,7 @@ export const filters = [
{
key: FilterKey.ERROR,
type: FilterType.MULTIPLE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'Error Message',
placeholder: 'E.g. Uncaught SyntaxError',
operator: 'is',
@ -197,53 +205,6 @@ export const filters = [
// { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true },
// FILTERS
{
key: FilterKey.USER_OS,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User OS',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/os'
},
{
key: FilterKey.USER_BROWSER,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User Browser',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/browser'
},
{
key: FilterKey.USER_DEVICE,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User Device',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/device'
},
{
key: FilterKey.PLATFORM,
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.GEAR,
label: 'Platform',
operator: 'is',
operatorOptions: filterOptions.baseOperators,
icon: 'filters/platform',
options: platformOptions
},
{
key: FilterKey.REVID,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'Version ID',
placeholder: 'E.g. v1.0.8',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'collection'
},
{
key: FilterKey.REFERRER,
type: FilterType.MULTIPLE,
@ -297,7 +258,7 @@ export const filters = [
key: FilterKey.USER_STATE,
type: FilterType.MULTIPLE,
category: FilterCategory.USER,
label: 'User State',
label: 'State / Province',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']),
icon: 'filters/country',
@ -429,15 +390,66 @@ export const filters = [
{
key: FilterKey.ISSUE,
type: FilterType.ISSUE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'Issue',
placeholder: 'Select an issue',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']),
icon: 'filters/click',
options: filterOptions.issueOptions
}
];
},
{
key: FilterKey.USER_OS,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User OS',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/os'
},
{
key: FilterKey.USER_BROWSER,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User Browser',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/browser'
},
{
key: FilterKey.USER_DEVICE,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'User Device',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'filters/device'
},
{
key: FilterKey.PLATFORM,
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.GEAR,
label: 'Platform',
operator: 'is',
operatorOptions: filterOptions.baseOperators,
icon: 'filters/platform',
options: platformOptions
},
{
key: FilterKey.REVID,
type: FilterType.MULTIPLE,
category: FilterCategory.GEAR,
label: 'Version ID',
placeholder: 'E.g. v1.0.8',
operator: 'is',
operatorOptions: filterOptions.stringOperators,
icon: 'collection'
},
].sort((a, b) => {
const aOrder = filterOrder[a.category] ?? 9
const bOrder = filterOrder[b.category] ?? 9
return aOrder - bOrder
})
export const flagConditionFilters = [
{
@ -500,7 +512,7 @@ export const flagConditionFilters = [
key: FilterKey.USER_STATE,
type: FilterType.MULTIPLE,
category: FilterCategory.USER,
label: 'User State',
label: 'State / Province',
operator: 'is',
operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']),
icon: 'filters/country',
@ -519,7 +531,11 @@ export const flagConditionFilters = [
}],
icon: 'filters/userid'
}
];
].sort((a, b) => {
const aOrder = filterOrder[a.category] ?? 9
const bOrder = filterOrder[b.category] ?? 9
return aOrder - bOrder
})
export const conditionalFilters = [
{
@ -546,7 +562,7 @@ export const conditionalFilters = [
{
key: FilterKey.CUSTOM,
type: FilterType.MULTIPLE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'Custom Events',
placeholder: 'Enter event key',
operator: 'is',
@ -557,7 +573,7 @@ export const conditionalFilters = [
{
key: FilterKey.FETCH,
type: FilterType.SUB_FILTERS,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
operator: 'is',
label: 'Network Request',
filters: [
@ -609,7 +625,7 @@ export const conditionalFilters = [
{
key: FilterKey.ERROR,
type: FilterType.MULTIPLE,
category: FilterCategory.JAVASCRIPT,
category: FilterCategory.TECHNICAL,
label: 'Error Message',
placeholder: 'E.g. Uncaught SyntaxError',
operator: 'is',
@ -636,7 +652,11 @@ export const conditionalFilters = [
operatorOptions: filterOptions.stringConditional,
isEvent: false
},
];
].sort((a, b) => {
const aOrder = filterOrder[a.category] ?? 9
const bOrder = filterOrder[b.category] ?? 9
return aOrder - bOrder
})
export const eventKeys = filters.filter((i) => i.isEvent).map(i => i.key);
export const nonFlagFilters = filters.filter(i => {
@ -671,7 +691,7 @@ const mapLiveFilters = (list) => {
list.forEach(filter => {
if (
filter.category !== FilterCategory.INTERACTIONS &&
filter.category !== FilterCategory.JAVASCRIPT &&
filter.category !== FilterCategory.TECHNICAL &&
filter.category !== FilterCategory.PERFORMANCE &&
filter.key !== FilterKey.DURATION &&
filter.key !== FilterKey.REFERRER &&