ui: table card creation fix, notif item swap, empty metadata
This commit is contained in:
parent
b09becdcb7
commit
0d9c265452
4 changed files with 127 additions and 45 deletions
|
|
@ -45,6 +45,7 @@ export interface CardType {
|
|||
height?: number;
|
||||
isEnterprise?: boolean;
|
||||
filters?: any;
|
||||
viewType?: string;
|
||||
}
|
||||
|
||||
export const CARD_LIST: CardType[] = [
|
||||
|
|
@ -159,7 +160,8 @@ export const CARD_LIST: CardType[] = [
|
|||
cardType: TABLE,
|
||||
metricOf: FilterKey.USER_COUNTRY,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: ByCountry
|
||||
example: ByCountry,
|
||||
viewType: 'table',
|
||||
},
|
||||
|
||||
{
|
||||
|
|
@ -168,7 +170,8 @@ export const CARD_LIST: CardType[] = [
|
|||
cardType: TABLE,
|
||||
metricOf: FilterKey.USER_DEVICE,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: BySystem
|
||||
example: BySystem,
|
||||
viewType: 'table',
|
||||
},
|
||||
{
|
||||
title: 'Untitled Top Pages',
|
||||
|
|
@ -176,7 +179,8 @@ export const CARD_LIST: CardType[] = [
|
|||
cardType: TABLE,
|
||||
metricOf: FilterKey.LOCATION,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: ByUrl
|
||||
example: ByUrl,
|
||||
viewType: 'table',
|
||||
},
|
||||
|
||||
{
|
||||
|
|
@ -185,7 +189,8 @@ export const CARD_LIST: CardType[] = [
|
|||
cardType: TABLE,
|
||||
metricOf: FilterKey.REFERRER,
|
||||
category: CARD_CATEGORIES[1].key,
|
||||
example: ByReferrer
|
||||
example: ByReferrer,
|
||||
viewType: 'table',
|
||||
},
|
||||
|
||||
// Monitors
|
||||
|
|
@ -202,7 +207,8 @@ export const CARD_LIST: CardType[] = [
|
|||
},
|
||||
width: 4,
|
||||
height: 336,
|
||||
example: TableOfErrors
|
||||
example: TableOfErrors,
|
||||
viewType: 'table',
|
||||
},
|
||||
{
|
||||
title: 'Untitled Top Network Requests',
|
||||
|
|
@ -210,7 +216,8 @@ export const CARD_LIST: CardType[] = [
|
|||
cardType: TABLE,
|
||||
metricOf: FilterKey.FETCH,
|
||||
category: CARD_CATEGORIES[2].key,
|
||||
example: ByFetch
|
||||
example: ByFetch,
|
||||
viewType: 'table',
|
||||
},
|
||||
{
|
||||
title: 'Untitled Sessions with 4xx/5xx Requests',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { NoContent, Loader, Pagination } from 'UI';
|
||||
import {Button, Tag, Tooltip, Dropdown, notification} from 'antd';
|
||||
import { Button, Tag, Tooltip, Dropdown, message } from 'antd';
|
||||
import {UndoOutlined, DownOutlined} from '@ant-design/icons'
|
||||
import cn from 'classnames';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
|
@ -65,11 +65,7 @@ function WidgetSessions(props: Props) {
|
|||
setData(res);
|
||||
if (metricStore.drillDown) {
|
||||
setTimeout(() => {
|
||||
notification.open({
|
||||
placement: 'top',
|
||||
role: 'status',
|
||||
message: 'Sessions Refreshed!'
|
||||
})
|
||||
message.info('Sessions Refreshed!')
|
||||
listRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
metricStore.setDrillDown(false);
|
||||
}, 0)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function WidgetView({ match: { params: { siteId, dashboardId, metricId } } }: Pr
|
|||
name: selectedCard.title,
|
||||
metricOf: selectedCard.metricOf,
|
||||
category: mk,
|
||||
viewType: selectedCard.cardType === FUNNEL ? 'chart' : 'lineChart',
|
||||
viewType: selectedCard.viewType ? selectedCard.viewType : selectedCard.cardType === FUNNEL ? 'chart' : 'lineChart',
|
||||
};
|
||||
if (selectedCard.filters) {
|
||||
cardData.series = [
|
||||
|
|
|
|||
|
|
@ -30,19 +30,21 @@ import {
|
|||
Workflow,
|
||||
Flag,
|
||||
ChevronRight,
|
||||
Info,
|
||||
SquareArrowOutUpRight
|
||||
} from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import { Input } from 'antd';
|
||||
import { Input, Button } from 'antd';
|
||||
|
||||
import { FilterCategory, FilterKey, FilterType } from "Types/filter/filterType";
|
||||
import { FilterCategory, FilterKey, FilterType } from 'Types/filter/filterType';
|
||||
import stl from './FilterModal.module.css';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useStore } from 'App/mstore';
|
||||
|
||||
export const IconMap = {
|
||||
[FilterKey.CLICK]: <Pointer size={14}/>,
|
||||
[FilterKey.CLICK]: <Pointer size={14} />,
|
||||
[FilterKey.LOCATION]: <Navigation size={14} />,
|
||||
[FilterKey.INPUT]: <RectangleEllipsis size={14} />,
|
||||
[FilterKey.CUSTOM]: <Code size={14} />,
|
||||
|
|
@ -87,15 +89,17 @@ function filterJson(
|
|||
return Object.fromEntries(
|
||||
Object.entries(jsonObj)
|
||||
.map(([key, value]) => {
|
||||
const arr = value.filter((i: { key: string, isEvent: boolean, category: string }) => {
|
||||
if (excludeCategory.includes(i.category)) return false;
|
||||
if (excludeKeys.includes(i.key)) return false;
|
||||
if (mode === 'events' && !i.isEvent) return false;
|
||||
if (mode === 'filters' && i.isEvent) return false;
|
||||
return !(
|
||||
allowedFilterKeys.length > 0 && !allowedFilterKeys.includes(i.key)
|
||||
);
|
||||
});
|
||||
const arr = value.filter(
|
||||
(i: { key: string; isEvent: boolean; category: string }) => {
|
||||
if (excludeCategory.includes(i.category)) return false;
|
||||
if (excludeKeys.includes(i.key)) return false;
|
||||
if (mode === 'events' && !i.isEvent) return false;
|
||||
if (mode === 'filters' && i.isEvent) return false;
|
||||
return !(
|
||||
allowedFilterKeys.length > 0 && !allowedFilterKeys.includes(i.key)
|
||||
);
|
||||
}
|
||||
);
|
||||
return [key, arr];
|
||||
})
|
||||
.filter(([_, arr]) => arr.length > 0)
|
||||
|
|
@ -130,7 +134,10 @@ export const getMatchingEntries = (
|
|||
}
|
||||
});
|
||||
|
||||
return { matchingCategories: ['All', ...matchingCategories], matchingFilters };
|
||||
return {
|
||||
matchingCategories: ['All', ...matchingCategories],
|
||||
matchingFilters,
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
|
|
@ -190,16 +197,22 @@ function FilterModal(props: Props) {
|
|||
: searchStore.loadingFilterSearch;
|
||||
|
||||
const parseAndAdd = (filter) => {
|
||||
if (filter.category === FilterCategory.EVENTS && filter.key.startsWith('_')) {
|
||||
if (
|
||||
filter.category === FilterCategory.EVENTS &&
|
||||
filter.key.startsWith('_')
|
||||
) {
|
||||
filter.value = [filter.key.substring(1)];
|
||||
filter.key = FilterKey.CUSTOM;
|
||||
filter.label = 'Custom Events'
|
||||
filter.label = 'Custom Events';
|
||||
}
|
||||
if (filter.type === FilterType.ISSUE && filter.key.startsWith(`${FilterKey.ISSUE}_`)) {
|
||||
if (
|
||||
filter.type === FilterType.ISSUE &&
|
||||
filter.key.startsWith(`${FilterKey.ISSUE}_`)
|
||||
) {
|
||||
filter.key = FilterKey.ISSUE;
|
||||
}
|
||||
onFilterClick(filter)
|
||||
}
|
||||
onFilterClick(filter);
|
||||
};
|
||||
const onFilterSearchClick = (filter: any) => {
|
||||
const _filter = { ...filtersMap[filter.type] };
|
||||
_filter.value = [filter.value];
|
||||
|
|
@ -211,9 +224,17 @@ function FilterModal(props: Props) {
|
|||
? mobileConditionalFilters
|
||||
: conditionalFilters
|
||||
: filters;
|
||||
const filterObj = filterJson(
|
||||
filterJsonObj,
|
||||
excludeFilterKeys,
|
||||
excludeCategory,
|
||||
allowedFilterKeys,
|
||||
mode
|
||||
);
|
||||
const hasNoMeta = !filterObj['Metadata'];
|
||||
const { matchingCategories, matchingFilters } = getMatchingEntries(
|
||||
searchQuery,
|
||||
filterJson(filterJsonObj, excludeFilterKeys, excludeCategory, allowedFilterKeys, mode)
|
||||
filterObj
|
||||
);
|
||||
|
||||
const isResultEmpty =
|
||||
|
|
@ -229,12 +250,11 @@ function FilterModal(props: Props) {
|
|||
: matchingFilters[category];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={stl.wrapper}
|
||||
style={{ width: '560px', maxHeight: '380px' }}
|
||||
>
|
||||
<div className={stl.wrapper} style={{ width: '560px', maxHeight: '380px' }}>
|
||||
<Input
|
||||
className={'mb-4 rounded-xl text-lg font-medium placeholder:text-lg placeholder:font-medium placeholder:text-neutral-300'}
|
||||
className={
|
||||
'mb-4 rounded-xl text-lg font-medium placeholder:text-lg placeholder:font-medium placeholder:text-neutral-300'
|
||||
}
|
||||
placeholder={'Search'}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
|
|
@ -246,17 +266,32 @@ function FilterModal(props: Props) {
|
|||
<div
|
||||
key={key}
|
||||
onClick={() => setCategory(key)}
|
||||
className={cn('rounded-xl px-4 py-2 hover:bg-active-blue capitalize cursor-pointer font-medium', key === category ? 'bg-active-blue text-teal' : '')}
|
||||
className={cn(
|
||||
'rounded-xl px-4 py-2 hover:bg-active-blue capitalize cursor-pointer font-medium',
|
||||
key === category ? 'bg-active-blue text-teal' : ''
|
||||
)}
|
||||
>
|
||||
{key}
|
||||
</div>
|
||||
))}
|
||||
{hasNoMeta ? (
|
||||
<div
|
||||
key={'META_CTA'}
|
||||
onClick={() => setCategory('META_CTA')}
|
||||
className={cn(
|
||||
'rounded-xl px-4 py-2 hover:bg-active-blue capitalize cursor-pointer font-medium',
|
||||
'META_CTA' === category ? 'bg-active-blue text-teal' : ''
|
||||
)}
|
||||
>
|
||||
Metadata
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className={'flex flex-col gap-1 overflow-y-auto w-full'}
|
||||
className={'flex flex-col gap-1 overflow-y-auto w-full h-full'}
|
||||
style={{ maxHeight: 300, flex: 2 }}
|
||||
>
|
||||
{displayedFilters.length
|
||||
{displayedFilters && displayedFilters.length
|
||||
? displayedFilters.map((filter: Record<string, any>) => (
|
||||
<div
|
||||
key={filter.label}
|
||||
|
|
@ -265,17 +300,61 @@ function FilterModal(props: Props) {
|
|||
)}
|
||||
onClick={() => parseAndAdd({ ...filter })}
|
||||
>
|
||||
{filter.category ? <div style={{ width: 100 }} className={'text-neutral-500/90 w-full flex justify-between items-center'}>
|
||||
<span>{filter.subCategory ? filter.subCategory : filter.category}</span>
|
||||
<ChevronRight size={14} />
|
||||
</div> : null}
|
||||
{filter.category ? (
|
||||
<div
|
||||
style={{ width: 100 }}
|
||||
className={
|
||||
'text-neutral-500/90 w-full flex justify-between items-center'
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{filter.subCategory
|
||||
? filter.subCategory
|
||||
: filter.category}
|
||||
</span>
|
||||
<ChevronRight size={14} />
|
||||
</div>
|
||||
) : null}
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<span className='text-neutral-500/90 text-xs'>{getNewIcon(filter)}</span>
|
||||
<span className="text-neutral-500/90 text-xs">
|
||||
{getNewIcon(filter)}
|
||||
</span>
|
||||
<span>{filter.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
: null}
|
||||
{category === 'META_CTA' && hasNoMeta ? (
|
||||
<div
|
||||
style={{
|
||||
height: 300
|
||||
}}
|
||||
className={
|
||||
'mx-auto flex flex-col items-center justify-center gap-4 w-2/3 text-center'
|
||||
}
|
||||
>
|
||||
<div className={'text-lg font-semibold flex gap-2 items-center'}>
|
||||
<Info size={16} />
|
||||
<span>No Metadata Available</span>
|
||||
</div>
|
||||
<div className={'text-disabled-text'}>
|
||||
Identify sessions & data easily by linking user-specific
|
||||
metadata.
|
||||
</div>
|
||||
<Button
|
||||
type={'text'}
|
||||
onClick={() => {
|
||||
const docs = 'https://docs.openreplay.com/en/installation/metadata/';
|
||||
window.open(docs, '_blank');
|
||||
}}
|
||||
>
|
||||
<div className={'flex items-center gap-2'}>
|
||||
<span className={'text-main'}>Learn how</span>
|
||||
<SquareArrowOutUpRight size={16} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{showSearchList && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue