ui: fix alert pre-init function, fix metric list options, fix legend placement

This commit is contained in:
nick-delirium 2025-01-15 15:18:18 +01:00
parent e7b4965ffb
commit 79f3cb7d98
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
9 changed files with 180 additions and 135 deletions

View file

@ -16,6 +16,7 @@ function BigNumChart(props: Props) {
label = 'Number of Sessions',
values,
onSeriesFocus,
hideLegend,
} = props;
return (
<div className={'pb-3'}>
@ -23,6 +24,7 @@ function BigNumChart(props: Props) {
{values.map((val, i) => (
<BigNum
key={i}
hideLegend={hideLegend}
color={colors[i]}
series={val.series}
value={val.value}
@ -37,7 +39,7 @@ function BigNumChart(props: Props) {
)
}
function BigNum({ color, series, value, label, compData, valueLabel, onSeriesFocus }: {
function BigNum({ color, series, value, label, compData, valueLabel, onSeriesFocus, hideLegend }: {
color: string,
series: string,
value: number,
@ -45,6 +47,7 @@ function BigNum({ color, series, value, label, compData, valueLabel, onSeriesFoc
compData?: number,
valueLabel?: string,
onSeriesFocus?: (name: string) => void
hideLegend?: boolean
}) {
const formattedNumber = (num: number) => {
return Intl.NumberFormat().format(num);
@ -66,21 +69,28 @@ function BigNum({ color, series, value, label, compData, valueLabel, onSeriesFoc
'hover:transition-all ease-in-out hover:ease-in-out hover:bg-teal/5 hover:cursor-pointer'
)}
>
<div className={'flex items-center gap-2 font-medium text-gray-darkest'}>
<div className={'rounded w-4 h-4'} style={{ background: color }} />
<div>{series}</div>
</div>
{hideLegend ? null :
<div
className={'flex items-center gap-2 font-medium text-gray-darkest'}
>
<div className={'rounded w-4 h-4'} style={{ background: color }} />
<div>{series}</div>
</div>
}
<div className={'font-bold leading-none'} style={{ fontSize: 56 }}>
{formattedNumber(value)}{valueLabel ? `${valueLabel}` : null}
</div>
<div className={'text-disabled-text text-xs'}>
{label}
{formattedNumber(value)}
{valueLabel ? `${valueLabel}` : null}
</div>
<div className={'text-disabled-text text-xs'}>{label}</div>
{compData ? (
<CompareTag isHigher={value > compData} absDelta={change} delta={changePercent} />
<CompareTag
isHigher={value > compData}
absDelta={change}
delta={changePercent}
/>
) : null}
</div>
)
);
}
export default BigNumChart;

View file

@ -1,4 +1,3 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import { useHistory } from 'react-router';
@ -105,7 +104,7 @@ function DashboardList() {
}
checkedChildren={'Team'}
unCheckedChildren={'Private'}
className='toggle-team-private'
className="toggle-team-private"
/>
</Tooltip>
</div>
@ -128,41 +127,48 @@ function DashboardList() {
dataIndex: 'dashboardId',
width: '5%',
render: (id) => (
<Dropdown
arrow={false}
trigger={['click']}
className={'ignore-prop-dp'}
menu={{
items: [
{
icon: <Icon name={'pencil'} />,
key: 'rename',
label: 'Rename',
<div onClick={(e) => e.stopPropagation()}>
<Dropdown
arrow={false}
trigger={['click']}
className={'ignore-prop-dp'}
menu={{
items: [
{
icon: <Icon name={'pencil'} />,
key: 'rename',
label: 'Rename',
},
{
icon: <Icon name={'users'} />,
key: 'access',
label: 'Visibility & Access',
},
{
icon: <Icon name={'trash'} />,
key: 'delete',
label: 'Delete',
},
],
onClick: async ({ key }) => {
if (key === 'rename') {
onEdit(id, true);
} else if (key === 'access') {
onEdit(id, false);
} else if (key === 'delete') {
await onDelete(id);
}
},
{
icon: <Icon name={'users'} />,
key: 'access',
label: 'Visibility & Access',
},
{
icon: <Icon name={'trash'} />,
key: 'delete',
label: 'Delete',
},
],
onClick: async ({ key }) => {
if (key === 'rename') {
onEdit(id, true);
} else if (key === 'access') {
onEdit(id, false);
} else if (key === 'delete') {
await onDelete(id);
}
},
}}
>
<Button id={'ignore-prop'} icon={<MoreOutlined />} type='text' className='btn-dashboards-list-item-more-options' />
</Dropdown>
}}
>
<Button
id={'ignore-prop'}
icon={<MoreOutlined />}
type="text"
className="btn-dashboards-list-item-more-options"
/>
</Dropdown>
</div>
),
},
];
@ -230,10 +236,12 @@ function DashboardList() {
onClick: (e) => {
const possibleDropdown =
document.querySelector('.ant-dropdown-menu');
const btn = document.querySelector('#ignore-prop');
if (
e.target.classList.contains('lucide') ||
e.target.id === 'ignore-prop' ||
possibleDropdown?.contains(e.target)
possibleDropdown?.contains(e.target) ||
btn?.contains(e.target)
) {
return;
}

View file

@ -253,6 +253,7 @@ function WidgetChart(props: Props) {
values={values}
inGrid={!props.isPreview}
colors={colors}
hideLegend
onClick={onChartClick}
label={
'Conversion'

View file

@ -159,30 +159,37 @@ const FilterSection = observer(({ layout, metric, excludeFilterKeys, excludeCate
/>
</div>
))}
<div className={'mx-auto flex items-center gap-2 w-fit'}>
<Tooltip title={canAddSeries ? '' : 'Maximum of 3 series reached.'}>
{isSingleSeries ? null :
<div className={'mx-auto flex items-center gap-2 w-fit'}>
<Tooltip title={canAddSeries ? '' : 'Maximum of 3 series reached.'}>
<Button
onClick={() => {
if (!canAddSeries) return;
metric.addSeries();
}}
disabled={!canAddSeries}
size="small"
type="primary"
icon={<PlusIcon size={16} />}
>
Add Series
</Button>
</Tooltip>
<Button
onClick={() => {
if (!canAddSeries) return;
metric.addSeries();
}}
disabled={!canAddSeries || isSingleSeries}
size="small"
type="primary"
icon={<PlusIcon size={16} />}
size={'small'}
type={'text'}
icon={
<ChevronUp
size={16}
className={allCollapsed ? 'rotate-180' : ''}
/>
}
onClick={allCollapsed ? expandAll : collapseAll}
>
Add Series
{allCollapsed ? 'Expand' : 'Collapse'} All
</Button>
</Tooltip>
<Button
size={'small'}
type={'text'}
icon={<ChevronUp size={16} className={allCollapsed ? 'rotate-180' : ''} />}
onClick={allCollapsed ? expandAll : collapseAll}
>
{allCollapsed ? 'Expand' : 'Collapse'} All
</Button>
</div>
</div>
}
</>
);
});

View file

@ -12,16 +12,15 @@ interface Props {
}
function AlertButton(props: Props) {
const {seriesId} = props;
const {dashboardStore, alertsStore} = useStore();
const {openModal, closeModal} = useModal();
const { seriesId, initAlert } = props;
const { alertsStore } = useStore();
const { openModal, closeModal } = useModal();
const onClick = () => {
// dashboardStore.toggleAlertModal(true);
alertsStore.init({query: {left: seriesId}})
initAlert?.();
alertsStore.init({ query: { left: seriesId } })
openModal(<AlertFormModal
onClose={closeModal}
/>, {
// title: 'Set Alerts',
placement: 'right',
width: 620,
});

View file

@ -36,7 +36,7 @@ interface Props {
}
function WidgetWrapperNew(props: Props & RouteComponentProps) {
const { dashboardStore } = useStore();
const { dashboardStore, metricStore } = useStore();
const {
isWidget = false,
active = false,
@ -94,6 +94,9 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
widget.metricOf !== FilterKey.ERRORS &&
widget.metricOf !== FilterKey.SESSIONS);
const beforeAlertInit = () => {
metricStore.init(widget)
}
return (
<Card
className={cn(
@ -113,7 +116,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) {
extra={[
<div className="flex items-center" id="no-print">
{!isPredefined && isTimeSeries && !isGridView && (
<AlertButton seriesId={widget.series[0] && widget.series[0].seriesId} />
<AlertButton initAlert={beforeAlertInit} seriesId={widget.series[0] && widget.series[0].seriesId} />
)}
{showMenu && (

View file

@ -112,7 +112,7 @@ export const getMatchingEntries = (
if (lowerCaseQuery.length === 0)
return {
matchingCategories: ['ALL', ...Object.keys(filters)],
matchingCategories: ['All', ...Object.keys(filters)],
matchingFilters: filters,
};
@ -130,7 +130,7 @@ export const getMatchingEntries = (
}
});
return { matchingCategories: ['ALL', ...matchingCategories], matchingFilters };
return { matchingCategories: ['All', ...matchingCategories], matchingFilters };
};
interface Props {
@ -171,7 +171,7 @@ function FilterModal(props: Props) {
mode,
} = props;
const [searchQuery, setSearchQuery] = React.useState('');
const [category, setCategory] = React.useState('ALL');
const [category, setCategory] = React.useState('All');
const { searchStore, searchStoreLive, projectsStore } = useStore();
const isMobile = projectsStore.active?.platform === 'ios'; // TODO - should be using mobile once the app is changed
const filters = isLive
@ -222,12 +222,14 @@ function FilterModal(props: Props) {
Object.keys(matchingFilters).length === 0;
const displayedFilters =
category === 'ALL'
category === 'All'
? Object.entries(matchingFilters).flatMap(([category, filters]) =>
filters.map((f: any) => ({ ...f, category }))
)
: matchingFilters[category];
console.log(displayedFilters)
return (
<div
className={stl.wrapper}
@ -247,7 +249,7 @@ function FilterModal(props: Props) {
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' : '')}
>
{key.toLowerCase()}
{key}
</div>
))}
</div>
@ -265,7 +267,7 @@ 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.category}</span>
<span>{filter.subCategory ? filter.subCategory : filter.category}</span>
<ChevronRight size={14} />
</div> : null}
<div className={'flex items-center gap-2'}>

View file

@ -60,27 +60,28 @@ class CustomFieldStore {
});
this.list = response.map((item_1: any) => new CustomField(item_1));
this.fetchedMetadata = true;
filterService.fetchTopValues('custom', undefined).then((response: []) => {
response.forEach((item: any) => {
const calls = [
addElementToFiltersMap,
addElementToFlagConditionsMap,
addElementToConditionalFiltersMap,
addElementToMobileConditionalFiltersMap,
];
calls.forEach((call) => {
call(
FilterCategory.EVENTS,
'_' + item.value,
FilterType.MULTIPLE,
'is',
filterOptions.stringOperators,
'filters/custom',
true
);
});
});
});
// custom_event values fetcher; turned off for now; useful for later
// filterService.fetchTopValues('custom', undefined).then((response: []) => {
// response.forEach((item: any) => {
// const calls = [
// addElementToFiltersMap,
// addElementToFlagConditionsMap,
// addElementToConditionalFiltersMap,
// addElementToMobileConditionalFiltersMap,
// ];
// calls.forEach((call) => {
// call(
// FilterCategory.EVENTS,
// '_' + item.value,
// FilterType.MULTIPLE,
// 'is',
// filterOptions.stringOperators,
// 'filters/custom',
// true
// );
// });
// });
// });
} finally {
this.isLoading = false;
}

View file

@ -11,19 +11,21 @@ const countryOptions = Object.keys(countries).map(i => ({ label: countries[i], v
const containsFilters = [{ key: 'contains', label: 'contains', text: 'contains', value: 'contains' }];
const filterOrder = {
[FilterCategory.AUTOCAPTURE]: 0,
[FilterCategory.DEVTOOLS]: 1,
[FilterCategory.USER]: 2,
[FilterCategory.SESSION]: 3,
[FilterCategory.ISSUE]: 4,
[FilterCategory.METADATA]: 5
[FilterCategory.EVENTS]: 0,
[FilterCategory.AUTOCAPTURE]: 1,
[FilterCategory.DEVTOOLS]: 2,
[FilterCategory.USER]: 3,
[FilterCategory.SESSION]: 4,
[FilterCategory.ISSUE]: 5,
[FilterCategory.METADATA]: 6
};
export const mobileFilters = [
{
key: FilterKey.CLICK_MOBILE,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Tap',
operator: 'on',
operatorOptions: filterOptions.targetOperators,
@ -33,7 +35,8 @@ export const mobileFilters = [
{
key: FilterKey.INPUT_MOBILE,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Text Input',
placeholder: 'Enter input label name',
operator: 'is',
@ -44,7 +47,8 @@ export const mobileFilters = [
{
key: FilterKey.VIEW_MOBILE,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Screen',
placeholder: 'Enter screen name',
operator: 'is',
@ -55,7 +59,7 @@ export const mobileFilters = [
{
key: FilterKey.CUSTOM_MOBILE,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
label: 'Custom Events',
placeholder: 'Enter event key',
operator: 'is',
@ -77,7 +81,8 @@ export const mobileFilters = [
{
key: FilterKey.SWIPE_MOBILE,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Swipe',
operator: 'on',
operatorOptions: filterOptions.targetOperators,
@ -103,7 +108,8 @@ export const filters = [
{
key: FilterKey.CLICK,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Click',
operator: 'on',
operatorOptions: filterOptions.targetOperators.concat(clickSelectorOperators),
@ -113,7 +119,8 @@ export const filters = [
{
key: FilterKey.INPUT,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Text Input',
placeholder: 'Enter input label name',
operator: 'is',
@ -124,7 +131,8 @@ export const filters = [
{
key: FilterKey.LOCATION,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Visited URL',
placeholder: 'Enter path',
operator: 'is',
@ -132,6 +140,18 @@ export const filters = [
icon: 'filters/location',
isEvent: true
},
{
key: FilterKey.TAGGED_ELEMENT,
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Tagged Element',
operator: 'is',
isEvent: true,
icon: 'filters/tag-element',
operatorOptions: filterOptions.tagElementOperators,
options: []
},
{
key: FilterKey.CUSTOM,
type: FilterType.MULTIPLE,
@ -306,17 +326,6 @@ export const filters = [
operatorOptions: filterOptions.getOperatorsByKeys(['is']),
icon: 'filters/duration'
},
{
key: FilterKey.TAGGED_ELEMENT,
type: FilterType.MULTIPLE_DROPDOWN,
category: FilterCategory.AUTOCAPTURE,
label: 'Tagged Element',
operator: 'is',
isEvent: true,
icon: 'filters/tag-element',
operatorOptions: filterOptions.tagElementOperators,
options: []
},
{
key: FilterKey.UTM_SOURCE,
type: FilterType.MULTIPLE,
@ -587,7 +596,8 @@ export const conditionalFilters = [
{
key: FilterKey.CLICK,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Click',
operator: 'on',
operatorOptions: filterOptions.targetConditional,
@ -597,7 +607,8 @@ export const conditionalFilters = [
{
key: FilterKey.LOCATION,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Visited URL',
placeholder: 'Enter path',
operator: 'is',
@ -817,7 +828,8 @@ export const mobileConditionalFilters = [
{
key: 'viewComponent',
type: FilterType.STRING,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'View on screen',
placeholder: 'View Name',
operator: 'is',
@ -857,7 +869,8 @@ export const mobileConditionalFilters = [
{
key: 'clickEvent',
type: FilterType.STRING,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Tap on view',
placeholder: 'View Name',
operator: 'is',
@ -887,7 +900,8 @@ export const nonConditionalFlagFilters = filters.filter(i => {
export const clickmapFilter = {
key: FilterKey.LOCATION,
type: FilterType.MULTIPLE,
category: FilterCategory.AUTOCAPTURE,
category: FilterCategory.EVENTS,
subCategory: FilterCategory.AUTOCAPTURE,
label: 'Visited URL', placeholder: 'Enter URL or path',
operator: filterOptions.pageUrlOperators[0].value,
operatorOptions: filterOptions.pageUrlOperators,
@ -910,7 +924,7 @@ const mapLiveFilters = (list) => {
const obj = {};
list.forEach(filter => {
if (
filter.category !== FilterCategory.AUTOCAPTURE &&
filter.category !== FilterCategory.EVENTS &&
filter.category !== FilterCategory.DEVTOOLS &&
filter.key !== FilterKey.DURATION &&
filter.key !== FilterKey.REFERRER &&