Merge branch 'dashboards-redesign' of https://github.com/openreplay/openreplay into dashboards-redesign
This commit is contained in:
commit
aad75771b3
14 changed files with 315 additions and 159 deletions
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import {Card, Col, Modal, Row, Typography} from "antd";
|
||||
import {Grid2x2CheckIcon, Plus} from "lucide-react";
|
||||
import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal";
|
||||
import {useStore} from "App/mstore";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
|
@ -9,6 +10,7 @@ interface Props {
|
|||
}
|
||||
|
||||
function AddCardSelectionModal(props: Props) {
|
||||
const {metricStore} = useStore();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [isLibrary, setIsLibrary] = React.useState(false);
|
||||
|
||||
|
|
@ -18,6 +20,9 @@ function AddCardSelectionModal(props: Props) {
|
|||
}
|
||||
|
||||
const onClick = (isLibrary: boolean) => {
|
||||
if (!isLibrary) {
|
||||
metricStore.init();
|
||||
}
|
||||
setIsLibrary(isLibrary);
|
||||
setOpen(true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
}
|
||||
|
||||
function CreateDashboardButton({disabled = false}: Props) {
|
||||
const [showModal, setShowModal] = React.useState(true);
|
||||
const [showModal, setShowModal] = React.useState(false);
|
||||
|
||||
return <>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import ExampleFunnel from "./Examples/Funnel";
|
|||
import ExamplePath from "./Examples/Path";
|
||||
import ExampleTrend from "./Examples/Trend";
|
||||
import PerfBreakdown from "./Examples/PerfBreakdown";
|
||||
import BarChartCard from "./Examples/BarChart";
|
||||
import SlowestDomain from "./Examples/SlowestDomain";
|
||||
import ByBrowser from "./Examples/SessionsBy/ByBrowser";
|
||||
import BySystem from "./Examples/SessionsBy/BySystem";
|
||||
|
|
@ -315,21 +316,8 @@ export const CARD_LIST: CardType[] = [
|
|||
metricOf: FilterKey.ERRORS_PER_DOMAINS,
|
||||
category: CARD_CATEGORIES[3].key,
|
||||
example: Bars,
|
||||
data: {
|
||||
total: 90,
|
||||
values: [
|
||||
{
|
||||
"label": "company.domain.com",
|
||||
"value": 89
|
||||
},
|
||||
{
|
||||
"label": "openreplay.com",
|
||||
"value": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
data: generateRandomBarsData(),
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Errors by Type',
|
||||
key: FilterKey.ERRORS_PER_TYPE,
|
||||
|
|
@ -375,6 +363,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
{
|
||||
|
|
@ -385,6 +374,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -396,6 +386,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -407,6 +398,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -418,6 +410,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -429,6 +422,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -440,6 +434,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -451,6 +446,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -462,6 +458,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -473,6 +470,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -484,6 +482,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
{
|
||||
|
|
@ -494,6 +493,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -505,6 +505,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -516,6 +517,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -527,6 +529,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -538,6 +541,7 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
|
||||
|
|
@ -549,6 +553,38 @@ export const CARD_LIST: CardType[] = [
|
|||
category: CARD_CATEGORIES[4].key,
|
||||
width: 1,
|
||||
height: 148,
|
||||
data: generateWebVitalData(),
|
||||
example: WebVital,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
function generateRandomBarsData(): { total: number, values: { label: string, value: number }[] } {
|
||||
const labels = ["company.domain.com", "openreplay.com"];
|
||||
const values = labels.map(label => ({
|
||||
label,
|
||||
value: Math.floor(Math.random() * 100)
|
||||
}));
|
||||
const total = values.reduce((acc, curr) => acc + curr.value, 0);
|
||||
|
||||
return {
|
||||
total,
|
||||
values: values.sort((a, b) => b.value - a.value)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function generateWebVitalData(): { value: number, chart: { timestamp: number, value: number }[], unit: string } {
|
||||
const chart = Array.from({length: 7}, (_, i) => ({
|
||||
timestamp: Date.now() + i * 86400000,
|
||||
value: parseFloat((Math.random() * 10).toFixed(15))
|
||||
}));
|
||||
|
||||
const value = chart.reduce((acc, curr) => acc + curr.value, 0) / chart.length;
|
||||
|
||||
return {
|
||||
value,
|
||||
chart,
|
||||
unit: "%"
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import {GitCommitHorizontal} from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import ExCard from './ExCard';
|
||||
import {PERFORMANCE} from "App/constants/card";
|
||||
import {Bar, BarChart, CartesianGrid, Legend, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts";
|
||||
import {Styles} from "Components/Dashboard/Widgets/common";
|
||||
|
||||
const _data = [
|
||||
{
|
||||
name: 'Jan',
|
||||
uv: 4000,
|
||||
pv: 2400,
|
||||
},
|
||||
{
|
||||
name: 'Feb',
|
||||
uv: 3000,
|
||||
pv: 1398,
|
||||
},
|
||||
{
|
||||
name: 'Mar',
|
||||
uv: 2000,
|
||||
pv: 9800,
|
||||
},
|
||||
{
|
||||
name: 'Apr',
|
||||
uv: 2780,
|
||||
pv: 3908,
|
||||
},
|
||||
{
|
||||
name: 'May',
|
||||
uv: 1890,
|
||||
pv: 4800,
|
||||
},
|
||||
{
|
||||
name: 'Jun',
|
||||
uv: 2390,
|
||||
pv: 3800,
|
||||
},
|
||||
{
|
||||
name: 'Jul',
|
||||
uv: 3490,
|
||||
pv: 4300,
|
||||
},
|
||||
];
|
||||
|
||||
function BarChartCard(props: any) {
|
||||
return (
|
||||
<ExCard
|
||||
{...props}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
width={400}
|
||||
height={280}
|
||||
data={_data}
|
||||
margin={Styles.chartMargins}
|
||||
>
|
||||
{/*<CartesianGrid strokeDasharray="3 3"/>*/}
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#EEEEEE"/>
|
||||
<XAxis {...Styles.xaxis} dataKey="name"/>
|
||||
<YAxis {...Styles.yaxis} />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Legend/>
|
||||
<Bar dataKey="pv" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue"/>}/>
|
||||
{/*<Bar dataKey="uv" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple"/>}/>*/}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</ExCard>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default BarChartCard;
|
||||
|
|
@ -19,7 +19,9 @@ function ExCard({
|
|||
style={{width: '100%', height: height || 286}}
|
||||
>
|
||||
<div className={'font-medium text-lg'}>{title}</div>
|
||||
<div className={'flex flex-col gap-2 mt-2 cursor-pointer'} onClick={() => onCard(type)}>{children}</div>
|
||||
<div className={'flex flex-col gap-2 mt-2 cursor-pointer'}
|
||||
style={{height: height ? height - 50 : 236,}}
|
||||
onClick={() => onCard(type)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ interface Props {
|
|||
title: string;
|
||||
type: string;
|
||||
onCard: (card: string) => void;
|
||||
data?: any,
|
||||
}
|
||||
|
||||
function WebVital(props: Props) {
|
||||
const data = {
|
||||
const data = props.data || {
|
||||
"value": 8.33316146432396,
|
||||
"chart": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {CARD_LIST, CARD_CATEGORIES, CardType} from './ExampleCards';
|
|||
import {useStore} from 'App/mstore';
|
||||
import Option from './Option';
|
||||
import CardsLibrary from "Components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary";
|
||||
import {FUNNEL} from "App/constants/card";
|
||||
|
||||
interface SelectCardProps {
|
||||
onClose: (refresh?: boolean) => void;
|
||||
|
|
@ -20,22 +21,33 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
|
|||
const dashboardId = window.location.pathname.split('/')[3];
|
||||
const [libraryQuery, setLibraryQuery] = React.useState<string>('');
|
||||
|
||||
|
||||
const handleCardSelection = (card: string) => {
|
||||
console.log('card', card);
|
||||
const selectedCard = CARD_LIST.find((c) => c.key === card) as CardType;
|
||||
metricStore.merge({
|
||||
|
||||
const cardData: any = {
|
||||
metricType: selectedCard.cardType,
|
||||
name: selectedCard.title,
|
||||
metricOf: selectedCard.metricOf,
|
||||
});
|
||||
};
|
||||
|
||||
if (selectedCard.cardType === FUNNEL) {
|
||||
cardData.series = []
|
||||
cardData.series.filter = []
|
||||
}
|
||||
|
||||
metricStore.merge(cardData);
|
||||
metricStore.instance.resetDefaults();
|
||||
onCard();
|
||||
};
|
||||
|
||||
const cardItems = useMemo(() => {
|
||||
return CARD_LIST.filter((card) => card.category === selected).map((card) => (
|
||||
<div key={card.key} className={card.width ? `col-span-${card.width}` : 'col-span-2'}>
|
||||
<card.example onCard={handleCardSelection} type={card.key} title={card.title} data={card.data} height={card.height}/>
|
||||
<card.example onCard={handleCardSelection}
|
||||
type={card.key}
|
||||
title={card.title}
|
||||
data={card.data}
|
||||
height={card.height}/>
|
||||
</div>
|
||||
));
|
||||
}, [selected]);
|
||||
|
|
@ -58,11 +70,6 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{/*<Header selectedCount={selectedCards.length}*/}
|
||||
{/* onAdd={onAddSelected}*/}
|
||||
{/* title={dashboardId ? (isLibrary ? "Add Card" : "Create Card") : "Select a template to create a card"}*/}
|
||||
{/*/>*/}
|
||||
|
||||
<Space className="items-center justify-between">
|
||||
<div className="text-xl leading-4 font-medium">
|
||||
{dashboardId ? (isLibrary ? "Add Card" : "Create Card") : "Select a template to create a card"}
|
||||
|
|
@ -77,7 +84,6 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
|
|||
|
||||
<Input.Search
|
||||
placeholder="Search"
|
||||
// onSearch={(value) => setLibraryQuery(value)}
|
||||
onChange={(value) => setLibraryQuery(value.target.value)}
|
||||
style={{width: 200}}
|
||||
/>
|
||||
|
|
@ -86,32 +92,17 @@ const SelectCard: React.FC<SelectCardProps> = (props: SelectCardProps) => {
|
|||
</Space>
|
||||
|
||||
{!isLibrary && <CategorySelector setSelected={setSelectedCategory} selected={selected}/>}
|
||||
{isLibrary ? <CardsLibrary query={libraryQuery} selectedList={selectedCards} category={selected}
|
||||
onCard={onCardClick}/> :
|
||||
|
||||
{isLibrary ?
|
||||
<CardsLibrary query={libraryQuery}
|
||||
selectedList={selectedCards}
|
||||
category={selected}
|
||||
onCard={onCardClick}/> :
|
||||
<ExampleCardsGrid items={cardItems}/>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// interface HeaderProps {
|
||||
// selectedCount?: number,
|
||||
// onAdd?: () => void;
|
||||
// title?: string;
|
||||
// }
|
||||
//
|
||||
// const Header: React.FC<HeaderProps> = ({title = '', selectedCount = 0, onAdd = () => null}) => (
|
||||
// <div className="flex items-center justify-between">
|
||||
// <div className="text-lg leading-4 font-semibold">{title}</div>
|
||||
// <div className="text-sm text-gray-500">
|
||||
// {selectedCount > 0 ? (
|
||||
// <Button type="link" onClick={onAdd}>
|
||||
// Add {selectedCount} Selected
|
||||
// </Button>
|
||||
// ) : ''}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
interface CategorySelectorProps {
|
||||
setSelected?: React.Dispatch<React.SetStateAction<string>>;
|
||||
selected?: string;
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ function FilterSeries(props: Props) {
|
|||
}
|
||||
|
||||
const onChangeEventsOrder = (_: any, {name, value}: any) => {
|
||||
console.log(name, value)
|
||||
series.filter.updateKey(name, value);
|
||||
observeChanges();
|
||||
};
|
||||
|
|
@ -115,7 +116,7 @@ function FilterSeries(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg shadow-sm bg-white ">
|
||||
<div className="border rounded-lg shadow-sm bg-white">
|
||||
{canExclude && <ExcludeFilters filter={series.filter}/>}
|
||||
|
||||
{!hideHeader && (
|
||||
|
|
@ -129,7 +130,7 @@ function FilterSeries(props: Props) {
|
|||
)}
|
||||
|
||||
{expandable && !expanded && (
|
||||
<Space className="justify-between w-full px-6 py-2">
|
||||
<Space className="justify-between w-full px-5 py-2">
|
||||
<FilterCountLabels filters={series.filter.filters} toggleExpand={() => setExpanded(!expanded)}/>
|
||||
<Button onClick={() => setExpanded(!expanded)}
|
||||
size="small"
|
||||
|
|
@ -162,7 +163,7 @@ function FilterSeries(props: Props) {
|
|||
)}
|
||||
</div>
|
||||
<div className="border-t h-12 flex items-center">
|
||||
<div className="-mx-4 px-6">
|
||||
<div className="-mx-4 px-5">
|
||||
<AddStepButton excludeFilterKeys={excludeFilterKeys} series={series}/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,27 @@ import React from 'react';
|
|||
import {Card, Space, Typography, Button} from "antd";
|
||||
import {useStore} from "App/mstore";
|
||||
import {eventKeys} from "Types/filter/newFilter";
|
||||
import {CLICKMAP, FUNNEL, INSIGHTS, RETENTION, TABLE, USER_PATH} from "App/constants/card";
|
||||
import {
|
||||
CLICKMAP,
|
||||
ERRORS,
|
||||
FUNNEL,
|
||||
INSIGHTS,
|
||||
PERFORMANCE,
|
||||
RESOURCE_MONITORING,
|
||||
RETENTION,
|
||||
TABLE,
|
||||
USER_PATH, WEB_VITALS
|
||||
} from "App/constants/card";
|
||||
import FilterSeries from "Components/Dashboard/components/FilterSeries/FilterSeries";
|
||||
import {metricOf} from "App/constants/filterOptions";
|
||||
import {AudioWaveform, ChevronDown, ChevronUp, PlusIcon} from "lucide-react";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import AddStepButton from "Components/Dashboard/components/FilterSeries/AddStepButton";
|
||||
import {Icon} from "UI";
|
||||
import FilterItem from "Shared/Filters/FilterItem";
|
||||
import {FilterKey} from "Types/filter/filterType";
|
||||
|
||||
function WidgetFormNew() {
|
||||
// const [expanded, setExpanded] = React.useState(true);
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const metric: any = metricStore.instance;
|
||||
|
||||
|
|
@ -20,9 +32,11 @@ function WidgetFormNew() {
|
|||
const isPathAnalysis = metric.metricType === USER_PATH;
|
||||
const excludeFilterKeys = isClickMap || isPathAnalysis ? eventKeys : [];
|
||||
const hasFilters = filtersLength > 0 || eventsLength > 0;
|
||||
const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(metric.metricType);
|
||||
|
||||
return (
|
||||
return isPredefined ? <PredefinedMessage/> : (
|
||||
<>
|
||||
<AdditionalFilters/>
|
||||
<Card
|
||||
styles={{
|
||||
body: {padding: '0'},
|
||||
|
|
@ -123,3 +137,37 @@ const FilterSection = observer(({metric, excludeFilterKeys}: any) => {
|
|||
</>
|
||||
);
|
||||
})
|
||||
|
||||
|
||||
const PathAnalysisFilter = observer(({metric}: any) => (
|
||||
<div className='form-group flex flex-col'>
|
||||
{metric.startType === 'start' ? 'Start Point' : 'End Point'}
|
||||
<FilterItem
|
||||
hideDelete
|
||||
filter={metric.startPoint}
|
||||
allowedFilterKeys={[FilterKey.LOCATION, FilterKey.CLICK, FilterKey.INPUT, FilterKey.CUSTOM]}
|
||||
onUpdate={val => metric.updateStartPoint(val)}
|
||||
onRemoveFilter={() => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
const AdditionalFilters = observer(() => {
|
||||
const {metricStore, dashboardStore, aiFiltersStore} = useStore();
|
||||
const metric: any = metricStore.instance;
|
||||
|
||||
return (
|
||||
<>
|
||||
{metric.metricType === USER_PATH && <PathAnalysisFilter metric={metric}/>}
|
||||
</>
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
const PredefinedMessage = () => (
|
||||
<div className='flex items-center my-6 justify-center'>
|
||||
<Icon name='info-circle' size='18' color='gray-medium'/>
|
||||
<div className='ml-2'>Filtering and drill-downs will be supported soon for this card type.</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import {observer} from "mobx-react-lite";
|
||||
import {Tooltip} from "UI";
|
||||
import {Segmented} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const EventsOrder = observer((props: {
|
||||
onChange: (e: any, v: any) => void,
|
||||
filter: any,
|
||||
}) => {
|
||||
const {filter, onChange} = props;
|
||||
const eventsOrderSupport = filter.eventsOrderSupport;
|
||||
const options = [
|
||||
{
|
||||
name: 'eventsOrder',
|
||||
label: 'THEN',
|
||||
value: 'then',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('then'),
|
||||
},
|
||||
{
|
||||
name: 'eventsOrder',
|
||||
label: 'AND',
|
||||
value: 'and',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('and'),
|
||||
},
|
||||
{
|
||||
name: 'eventsOrder',
|
||||
label: 'OR',
|
||||
value: 'or',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('or'),
|
||||
},
|
||||
];
|
||||
|
||||
return <div className="flex items-center gap-2">
|
||||
<div
|
||||
className="color-gray-medium text-sm"
|
||||
style={{textDecoration: "underline dotted"}}
|
||||
>
|
||||
<Tooltip
|
||||
title={`Select the operator to be applied between events in your search.`}
|
||||
>
|
||||
<div>Events Order</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Segmented
|
||||
size={"small"}
|
||||
className="text-sm"
|
||||
onChange={(v) => onChange(null, options.find((i) => i.value === v))}
|
||||
value={filter.eventsOrder}
|
||||
options={options}
|
||||
/>
|
||||
</div>;
|
||||
});
|
||||
|
||||
export default EventsOrder;
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import {Segmented} from 'antd';
|
||||
import {Space} from 'antd';
|
||||
import {List} from 'immutable';
|
||||
import {GripHorizontal} from 'lucide-react';
|
||||
import {observer} from 'mobx-react-lite';
|
||||
import React, {useEffect} from 'react';
|
||||
|
||||
import {Tooltip} from 'UI';
|
||||
|
||||
import FilterItem from '../FilterItem';
|
||||
import EventsOrder from "Shared/Filters/FilterList/EventsOrder";
|
||||
|
||||
interface Props {
|
||||
filter?: any; // event/filter
|
||||
|
|
@ -38,7 +37,6 @@ function FilterList(props: Props) {
|
|||
} = props;
|
||||
|
||||
const filters = List(filter.filters);
|
||||
const eventsOrderSupport = filter.eventsOrderSupport;
|
||||
const hasEvents = filters.filter((i: any) => i.isEvent).size > 0;
|
||||
const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0;
|
||||
|
||||
|
|
@ -111,25 +109,6 @@ function FilterList(props: Props) {
|
|||
[draggedInd, hoveredItem, filters, props.onFilterMove]
|
||||
);
|
||||
|
||||
const eventOrderItems = [
|
||||
{
|
||||
label: 'THEN',
|
||||
value: 'then',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('then'),
|
||||
|
||||
},
|
||||
{
|
||||
label: 'AND',
|
||||
value: 'and',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('and'),
|
||||
},
|
||||
{
|
||||
label: 'OR',
|
||||
value: 'or',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('or'),
|
||||
},
|
||||
];
|
||||
|
||||
const eventsNum = filters.filter((i: any) => i.isEvent).size
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
|
|
@ -137,37 +116,16 @@ function FilterList(props: Props) {
|
|||
<>
|
||||
<div className="flex items-center mb-2">
|
||||
<div className="text-sm color-gray-medium mr-auto">
|
||||
{filter.eventsHeader}
|
||||
{filter.eventsHeader || 'EVENTS'}
|
||||
</div>
|
||||
{!hideEventsOrder && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="color-gray-medium text-sm"
|
||||
style={{textDecoration: 'underline dotted'}}
|
||||
>
|
||||
<Tooltip
|
||||
title={`Select the operator to be applied between events in your search.`}
|
||||
>
|
||||
<div>Events Order</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Segmented
|
||||
size={'small'}
|
||||
onChange={(v) =>
|
||||
props.onChangeEventsOrder(
|
||||
null,
|
||||
eventOrderItems.find((i) => i.value === v)
|
||||
)
|
||||
}
|
||||
value={filter.eventsOrder}
|
||||
options={eventOrderItems}
|
||||
/>
|
||||
{actions && actions.map((action, index) => (
|
||||
<div key={index}>{action}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Space>
|
||||
{!hideEventsOrder && <EventsOrder filter={filter}
|
||||
onChange={props.onChangeEventsOrder}/>}
|
||||
{actions && actions.map((action, index) => (
|
||||
<div key={index}>{action}</div>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
<div className={'flex flex-col'}>
|
||||
{filters.map((filter: any, filterIndex: number) =>
|
||||
|
|
@ -263,50 +221,3 @@ function FilterList(props: Props) {
|
|||
}
|
||||
|
||||
export default observer(FilterList);
|
||||
|
||||
|
||||
function EventsOrder(props: {
|
||||
onChange: (e: any, v: any) => void,
|
||||
filter: any,
|
||||
eventsOrderSupport: any
|
||||
}) {
|
||||
const {filter, eventsOrderSupport, onChange} = props;
|
||||
const options = [
|
||||
{
|
||||
label: 'THEN',
|
||||
value: 'then',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('then'),
|
||||
},
|
||||
{
|
||||
label: 'AND',
|
||||
value: 'and',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('and'),
|
||||
},
|
||||
{
|
||||
label: 'OR',
|
||||
value: 'or',
|
||||
disabled: eventsOrderSupport && !eventsOrderSupport.includes('or'),
|
||||
},
|
||||
];
|
||||
|
||||
return <div className="flex items-center gap-2">
|
||||
<div
|
||||
className="color-gray-medium text-sm"
|
||||
style={{textDecoration: "underline dotted"}}
|
||||
>
|
||||
<Tooltip
|
||||
title={`Select the operator to be applied between events in your search.`}
|
||||
>
|
||||
<div>Events Order</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Segmented
|
||||
size={"small"}
|
||||
// onChange={props.onChange}
|
||||
onChange={(v) => onChange(null, options.find((i) => i.value === v))}
|
||||
value={filter.eventsOrder}
|
||||
options={options}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { makeAutoObservable, runInAction, observable, action } from "mobx"
|
||||
import {makeAutoObservable, runInAction, observable, action} from "mobx"
|
||||
import FilterItem from "./filterItem"
|
||||
import { filtersMap, conditionalFiltersMap } from 'Types/filter/newFilter';
|
||||
import {filtersMap, conditionalFiltersMap} from 'Types/filter/newFilter';
|
||||
import {FilterKey} from "Types/filter/filterType";
|
||||
|
||||
export default class Filter {
|
||||
public static get ID_KEY():string { return "filterId" }
|
||||
public static get ID_KEY(): string {
|
||||
return "filterId"
|
||||
}
|
||||
|
||||
filterId: string = ''
|
||||
name: string = ''
|
||||
filters: FilterItem[] = []
|
||||
|
|
@ -70,7 +74,7 @@ export default class Filter {
|
|||
fromJson(json: any) {
|
||||
this.name = json.name
|
||||
this.filters = json.filters.map((i: Record<string, any>) =>
|
||||
new FilterItem(undefined, this.isConditional, this.isMobile).fromJson(i)
|
||||
new FilterItem(undefined, this.isConditional, this.isMobile).fromJson(i)
|
||||
);
|
||||
this.eventsOrder = json.eventsOrder
|
||||
return this
|
||||
|
|
@ -79,7 +83,7 @@ export default class Filter {
|
|||
fromData(data) {
|
||||
this.name = data.name
|
||||
this.filters = data.filters.map((i: Record<string, any>) =>
|
||||
new FilterItem(undefined, this.isConditional, this.isMobile).fromData(i)
|
||||
new FilterItem(undefined, this.isConditional, this.isMobile).fromData(i)
|
||||
)
|
||||
this.eventsOrder = data.eventsOrder
|
||||
return this
|
||||
|
|
@ -121,4 +125,10 @@ export default class Filter {
|
|||
removeExcludeFilter(index: number) {
|
||||
this.excludes.splice(index, 1)
|
||||
}
|
||||
|
||||
addFunnelDefaultFilters() {
|
||||
this.filters = []
|
||||
this.addFilter({...filtersMap[FilterKey.CLICK], value: [''], operator: 'onAny'})
|
||||
this.addFilter({...filtersMap[FilterKey.CLICK], value: [''], operator: 'onAny'})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,6 +265,16 @@ export default class Widget {
|
|||
});
|
||||
}
|
||||
|
||||
resetDefaults() {
|
||||
if (this.metricType === FUNNEL) {
|
||||
this.series = [];
|
||||
this.series.push(new FilterSeries());
|
||||
this.series[0].filter.addFunnelDefaultFilters();
|
||||
this.series[0].filter.eventsOrder = 'then';
|
||||
this.series[0].filter.eventsOrderSupport = ['then'];
|
||||
}
|
||||
}
|
||||
|
||||
exists() {
|
||||
return this.metricId !== undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,18 @@
|
|||
background-color: var(--bg-teal);
|
||||
}
|
||||
|
||||
:root{
|
||||
--bg-teal: #394dfe;
|
||||
}
|
||||
|
||||
.ant-btn{
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.ant-btn-primary{
|
||||
background-color: var(--bg-teal);
|
||||
}
|
||||
|
||||
.ml-15 { margin-left: 15px; }
|
||||
|
||||
.ph-10 { padding-left: 10px; padding-right: 10px; }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue