feat(ui) - custom metrics - wip
This commit is contained in:
parent
fd9f4bc0d3
commit
ed56d206be
22 changed files with 254 additions and 151 deletions
|
|
@ -240,7 +240,6 @@ export default class Dashboard extends React.PureComponent {
|
|||
Custom Metrics are not supported for comparison.
|
||||
</div>
|
||||
)}
|
||||
{/* <CustomMetrics /> */}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -8,15 +8,17 @@ interface Props {
|
|||
params: any;
|
||||
seriesMap: any;
|
||||
colors: any;
|
||||
onClick?: (event, index) => void;
|
||||
}
|
||||
function CustomMetriLineChart(props: Props) {
|
||||
const { data, params, seriesMap, colors } = props;
|
||||
const { data, params, seriesMap, colors, onClick = () => null } = props;
|
||||
return (
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<LineChart
|
||||
data={ data }
|
||||
margin={Styles.chartMargins}
|
||||
// syncId={ showSync ? "domainsErrors_4xx" : undefined }
|
||||
onClick={onClick}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetriPercentage';
|
||||
|
|
@ -2,13 +2,16 @@ import React from 'react'
|
|||
|
||||
interface Props {
|
||||
data: any;
|
||||
params: any;
|
||||
colors: any;
|
||||
onClick?: (event, index) => void;
|
||||
}
|
||||
function CustomMetriPercentage(props: Props) {
|
||||
const { data } = props;
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
|
||||
<div className="text-6xl">0%</div>
|
||||
<div className="text-lg mt-6">0 ( 0.0% ) from previous hour</div>
|
||||
<div className="text-6xl">{data.count}</div>
|
||||
<div className="text-lg mt-6">{`${data.previousCount} ( ${data.countProgress}% ) from previous hour`}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './CustomMetricPercentage';
|
||||
|
|
@ -1,15 +1,46 @@
|
|||
import React from 'react'
|
||||
|
||||
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LineChart, Line, Legend, PieChart, Pie } from 'recharts';
|
||||
import { Styles } from '../../common';
|
||||
interface Props {
|
||||
data: any;
|
||||
params: any;
|
||||
// seriesMap: any;
|
||||
colors: any;
|
||||
onClick?: (event, index) => void;
|
||||
}
|
||||
function CustomMetricPieChart(props: Props) {
|
||||
const { data } = props;
|
||||
const { data, params, colors, onClick = () => null } = props;
|
||||
const data01 = [
|
||||
{ "name": "Group A", "value": 400 },
|
||||
{ "name": "Group B", "value": 300 },
|
||||
{ "name": "Group C", "value": 300 },
|
||||
{ "name": "Group D", "value": 200 },
|
||||
{ "name": "Group E", "value": 278 },
|
||||
{ "name": "Group F", "value": 189 }
|
||||
];
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
|
||||
<div className="text-6xl">0%</div>
|
||||
<div className="text-lg mt-6">0 ( 0.0% ) from previous hour</div>
|
||||
</div>
|
||||
// <div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
|
||||
// <div className="text-6xl">0%</div>
|
||||
// <div className="text-lg mt-6">0 ( 0.0% ) from previous hour</div>
|
||||
// </div>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<PieChart width={730} height={250} onClick={onClick}>
|
||||
<Pie
|
||||
data={data01}
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={40}
|
||||
outerRadius={70}
|
||||
fill={colors[0]}
|
||||
activeIndex={1}
|
||||
label
|
||||
/>
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,22 +2,24 @@ import React, { useEffect, useState } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, Icon, Popup } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LineChart, Line, Legend } from 'recharts';
|
||||
import { ResponsiveContainer } from 'recharts';
|
||||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
import stl from './CustomMetricWidget.css';
|
||||
import { getChartFormatter, getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper';
|
||||
import { init, edit, remove, setAlertMetricId, setActiveWidget, updateActiveState } from 'Duck/customMetrics';
|
||||
import APIClient from 'App/api_client';
|
||||
import { setShowAlerts } from 'Duck/dashboard';
|
||||
import CustomMetriLineChart from '../CustomMetriLineChart';
|
||||
import CustomMetricPieChart from '../CustomMetricPieChart';
|
||||
import CustomMetricPercentage from '../CustomMetricPercentage';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
||||
if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
if (rangeName === YESTERDAY) params.density = 70
|
||||
if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
// if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
// if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
// if (rangeName === YESTERDAY) params.density = 70
|
||||
// if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
|
@ -47,11 +49,10 @@ function CustomMetricWidget(props: Props) {
|
|||
|
||||
const colors = Styles.customMetricColors;
|
||||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart', startDate: period.start, endDate: period.end }
|
||||
|
||||
useEffect(() => {
|
||||
new APIClient()['post']('/custom_metrics/chart', { ...metricParams, q: metric.name })
|
||||
new APIClient()['post'](`/custom_metrics/${metricParams.metricId}/chart`, { ...metricParams, q: metric.name })
|
||||
.then(response => response.json())
|
||||
.then(({ errors, data }) => {
|
||||
if (errors) {
|
||||
|
|
@ -78,8 +79,19 @@ function CustomMetricWidget(props: Props) {
|
|||
if (event) {
|
||||
const payload = event.activePayload[0].payload;
|
||||
const timestamp = payload.timestamp;
|
||||
const { startTimestamp, endTimestamp } = getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density);
|
||||
props.setActiveWidget({ widget: metric, startTimestamp, endTimestamp, timestamp: payload.timestamp, index })
|
||||
const periodTimestamps = metric.metricType === 'timeseries' ?
|
||||
getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) :
|
||||
period.toTimestamps();
|
||||
|
||||
const activeWidget = {
|
||||
widget: metric,
|
||||
period: period,
|
||||
...periodTimestamps,
|
||||
timestamp: payload.timestamp,
|
||||
index,
|
||||
}
|
||||
|
||||
props.setActiveWidget(activeWidget);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,49 +116,35 @@ function CustomMetricWidget(props: Props) {
|
|||
show={ data.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<LineChart
|
||||
data={ data }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "domainsErrors_4xx" : undefined }
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="colorCount" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={colors[4]} stopOpacity={ 0.9 } />
|
||||
<stop offset="95%" stopColor={colors[4]} stopOpacity={ 0.2 } />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" />
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Sessions"
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
{ seriesMap.map((key, index) => (
|
||||
<Line
|
||||
key={key}
|
||||
name={key}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
stroke={colors[index]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill="url(#colorCount)"
|
||||
dot={false}
|
||||
<>
|
||||
{metric.viewType === 'lineChart' && (
|
||||
<CustomMetriLineChart
|
||||
data={ data }
|
||||
params={ params }
|
||||
seriesMap={ seriesMap }
|
||||
colors={ colors }
|
||||
onClick={ clickHandler }
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
)}
|
||||
|
||||
{metric.viewType === 'pieChart' && (
|
||||
<CustomMetricPieChart
|
||||
data={ data }
|
||||
params={ params }
|
||||
colors={ colors }
|
||||
onClick={ clickHandler }
|
||||
/>
|
||||
)}
|
||||
|
||||
{metric.viewType === 'progress' && (
|
||||
<CustomMetricPercentage
|
||||
data={ data }
|
||||
params={ params }
|
||||
colors={ colors }
|
||||
onClick={ clickHandler }
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, SegmentSelection, Icon } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts';
|
||||
// import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip, LineChart, Line, Legend } from 'recharts';
|
||||
import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
import stl from './CustomMetricWidgetPreview.css';
|
||||
import { getChartFormatter } from 'Types/dashboard/helper';
|
||||
|
|
@ -10,10 +10,11 @@ import { remove } from 'Duck/customMetrics';
|
|||
import DateRange from 'Shared/DateRange';
|
||||
import { edit } from 'Duck/customMetrics';
|
||||
import CustomMetriLineChart from '../CustomMetriLineChart';
|
||||
import CustomMetriPercentage from '../CustomMetriPercentage';
|
||||
import CustomMetricPercentage from '../CustomMetricPercentage';
|
||||
import CustomMetricTable from '../CustomMetricTable';
|
||||
|
||||
import APIClient from 'App/api_client';
|
||||
import CustomMetricPieChart from '../CustomMetricPieChart';
|
||||
|
||||
const customParams = rangeName => {
|
||||
const params = { density: 70 }
|
||||
|
|
@ -46,8 +47,9 @@ function CustomMetricWidget(props: Props) {
|
|||
const params = customParams(period.rangeName)
|
||||
const gradientDef = Styles.gradientDef();
|
||||
const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' }
|
||||
|
||||
const prevMetricRef = useRef<any>();
|
||||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const isTable = metric.metricType === 'table';
|
||||
|
||||
useEffect(() => {
|
||||
// Check for title change
|
||||
|
|
@ -95,17 +97,35 @@ function CustomMetricWidget(props: Props) {
|
|||
<div className="flex items-center mb-4">
|
||||
<div className="mr-auto font-medium">Preview</div>
|
||||
<div className="flex items-center">
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
size="extraSmall"
|
||||
onSelect={ chagneViewType }
|
||||
value={{ value: metric.viewType }}
|
||||
list={ [
|
||||
{ value: 'chart', icon: 'graph-up-arrow' },
|
||||
{ value: 'percent', icon: 'hash' },
|
||||
]}
|
||||
/>
|
||||
{isTimeSeries && (
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
primary
|
||||
icons={true}
|
||||
onSelect={ chagneViewType }
|
||||
value={{ value: metric.viewType }}
|
||||
list={ [
|
||||
{ value: 'lineChart', icon: 'graph-up-arrow' },
|
||||
{ value: 'progress', icon: 'hash' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTable && (
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
primary={true}
|
||||
icons={true}
|
||||
onSelect={ chagneViewType }
|
||||
value={{ value: metric.viewType }}
|
||||
list={[
|
||||
{ value: 'table', icon: 'table' },
|
||||
{ value: 'pieChart', icon: 'graph-up-arrow' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="mx-2" />
|
||||
<span className="mr-1 color-gray-medium">Time Range</span>
|
||||
<DateRange
|
||||
|
|
@ -125,12 +145,16 @@ function CustomMetricWidget(props: Props) {
|
|||
size="small"
|
||||
show={ data.length === 0 }
|
||||
>
|
||||
{ metric.metricType === 'timeseries' && (
|
||||
{ isTimeSeries && (
|
||||
<>
|
||||
{ metric.viewType === 'percent' && (
|
||||
<CustomMetriPercentage data={data} />
|
||||
{ metric.viewType === 'progress' && (
|
||||
<CustomMetricPercentage
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)}
|
||||
{ metric.viewType === 'chart' && (
|
||||
{ metric.viewType === 'lineChart' && (
|
||||
<CustomMetriLineChart
|
||||
data={data}
|
||||
seriesMap={seriesMap}
|
||||
|
|
@ -141,9 +165,17 @@ function CustomMetricWidget(props: Props) {
|
|||
</>
|
||||
)}
|
||||
|
||||
{ metric.metricType === 'table' && (
|
||||
{ isTable && (
|
||||
<div className="p-3">
|
||||
<CustomMetricTable data={data} />
|
||||
{ metric.viewType === 'table' ? (
|
||||
<CustomMetricTable data={data} />
|
||||
) : (
|
||||
<CustomMetricPieChart
|
||||
data={data}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</NoContent>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import CustomMetricWidget from './CustomMetricWidget';
|
|||
import AlertFormModal from 'App/components/Alerts/AlertFormModal';
|
||||
import { init as initAlert } from 'Duck/alerts';
|
||||
import LazyLoad from 'react-lazyload';
|
||||
import CustomMetrics from 'App/components/shared/CustomMetrics';
|
||||
|
||||
interface Props {
|
||||
fetchList: Function;
|
||||
|
|
@ -22,7 +23,7 @@ function CustomMetricsWidgets(props: Props) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{list.filter(item => item.active).map((item: any) => (
|
||||
{list.map((item: any) => (
|
||||
<LazyLoad>
|
||||
<CustomMetricWidget
|
||||
key={item.metricId}
|
||||
|
|
@ -36,6 +37,13 @@ function CustomMetricsWidgets(props: Props) {
|
|||
</LazyLoad>
|
||||
))}
|
||||
|
||||
{list.size === 0 && (
|
||||
<div className="flex items-center py-2">
|
||||
<div className="mr-2">Be proactive by monitoring the metrics you care about the most.</div>
|
||||
<CustomMetrics />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AlertFormModal
|
||||
showModal={!!activeMetricId}
|
||||
metricId={activeMetricId}
|
||||
|
|
@ -46,5 +54,5 @@ function CustomMetricsWidgets(props: Props) {
|
|||
}
|
||||
|
||||
export default connect(state => ({
|
||||
list: state.getIn(['customMetrics', 'list']),
|
||||
list: state.getIn(['customMetrics', 'list']).filter(item => item.active),
|
||||
}), { fetchList, initAlert })(CustomMetricsWidgets);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Form, SegmentSelection, Button, IconButton } from 'UI';
|
||||
import { Form, Button, IconButton } from 'UI';
|
||||
import FilterSeries from '../FilterSeries';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit as editMetric, save, addSeries, removeSeries, remove } from 'Duck/customMetrics';
|
||||
|
|
@ -8,8 +8,7 @@ import { confirm } from 'UI/Confirmation';
|
|||
import { toast } from 'react-toastify';
|
||||
import cn from 'classnames';
|
||||
import DropdownPlain from '../../DropdownPlain';
|
||||
import { metricTypes, metricOf } from 'App/constants/filterOptions';
|
||||
|
||||
import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions';
|
||||
interface Props {
|
||||
metric: any;
|
||||
editMetric: (metric, shouldFetch?) => void;
|
||||
|
|
@ -23,7 +22,7 @@ interface Props {
|
|||
|
||||
function CustomMetricForm(props: Props) {
|
||||
const { metric, loading } = props;
|
||||
const metricOfOptions = metricOf.filter(i => i.key === metric.metricType);
|
||||
// const metricOfOptions = metricOf.filter(i => i.key === metric.metricType);
|
||||
const timeseriesOptions = metricOf.filter(i => i.key === 'timeseries');
|
||||
const tableOptions = metricOf.filter(i => i.key === 'table');
|
||||
|
||||
|
|
@ -39,18 +38,28 @@ function CustomMetricForm(props: Props) {
|
|||
const writeOption = (e, { value, name }) => {
|
||||
props.editMetric({ [ name ]: value }, false);
|
||||
|
||||
if (name === 'metricValue') {
|
||||
props.editMetric({ metricValue: [value] }, false);
|
||||
}
|
||||
|
||||
if (name === 'metricOf') {
|
||||
if (value === 'ISSUES') {
|
||||
props.editMetric({ metricValue: [issueOptions[0].value] }, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'metricType') {
|
||||
if (value === 'timeseries') {
|
||||
props.editMetric({ metricOf: timeseriesOptions[0].value }, false);
|
||||
props.editMetric({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false);
|
||||
} else if (value === 'table') {
|
||||
props.editMetric({ metricOf: tableOptions[0].value }, false);
|
||||
props.editMetric({ metricOf: tableOptions[0].value, viewType: 'table' }, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const changeConditionTab = (e, { name, value }) => {
|
||||
props.editMetric({[ 'viewType' ]: value });
|
||||
};
|
||||
// const changeConditionTab = (e, { name, value }) => {
|
||||
// props.editMetric({[ 'viewType' ]: value });
|
||||
// };
|
||||
|
||||
const save = () => {
|
||||
props.save(metric).then(() => {
|
||||
|
|
@ -126,38 +135,32 @@ function CustomMetricForm(props: Props) {
|
|||
</>
|
||||
)}
|
||||
|
||||
{metric.metricOf === 'ISSUES' && (
|
||||
<>
|
||||
<span className="mx-3">issue type</span>
|
||||
<DropdownPlain
|
||||
name="metricValue"
|
||||
options={issueOptions}
|
||||
value={ metric.metricValue[0] }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{metric.metricType === 'table' && (
|
||||
<>
|
||||
<span className="mx-3">showing</span>
|
||||
<DropdownPlain
|
||||
name="viewType"
|
||||
name="metricFormat"
|
||||
options={[
|
||||
{ value: 'sessionCount', text: 'Session Count' },
|
||||
]}
|
||||
value={ metric.viewType }
|
||||
value={ metric.metricFormat }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* <div className="flex items-center">
|
||||
<span className="bg-white p-1 px-2 border rounded" style={{ height: '30px'}}>Timeseries</span>
|
||||
<span className="mx-2 color-gray-medium">of</span>
|
||||
<div>
|
||||
<SegmentSelection
|
||||
primary
|
||||
name="viewType"
|
||||
small={true}
|
||||
// className="my-3"
|
||||
onSelect={ changeConditionTab }
|
||||
value={{ value: metric.viewType }}
|
||||
list={ [
|
||||
{ name: 'Session Count', value: 'lineChart' },
|
||||
{ name: 'Session Percentage', value: 'progress', disabled: true },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ function FilterSeries(props: Props) {
|
|||
<div className="color-gray-medium">Add user event or filter to define the series by clicking Add Step.</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="px-5 border-t h-12 flex items-center">
|
||||
<div className="px-6 border-t h-12 flex items-center -mx-4">
|
||||
<FilterSelection
|
||||
filter={undefined}
|
||||
onFilterClick={onAddFilter}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import stl from './SessionListModal.css';
|
|||
import { connect } from 'react-redux';
|
||||
import { fetchSessionList, setActiveWidget } from 'Duck/customMetrics';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
list: any;
|
||||
|
|
@ -57,9 +56,9 @@ function SessionListModal(props: Props) {
|
|||
|
||||
const writeOption = (e, { name, value }) => setActiveSeries(value);
|
||||
const filteredSessions = getListSessionsBySeries(activeSeries);
|
||||
|
||||
const startTime = DateTime.fromMillis(activeWidget.startTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
const endTime = DateTime.fromMillis(activeWidget.endTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
|
||||
return (
|
||||
<SlideModal
|
||||
title={ activeWidget && (
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ interface Props {
|
|||
icon?: string;
|
||||
direction?: string;
|
||||
value: any;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
export default function DropdownPlain(props: Props) {
|
||||
const { name = "sort", value, options, icon = "chevron-down", direction = "right" } = props;
|
||||
const { name = "sort", value, options, icon = "chevron-down", direction = "right", multiple = false } = props;
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
|
|
@ -24,6 +25,7 @@ export default function DropdownPlain(props: Props) {
|
|||
onChange={ props.onChange }
|
||||
// floating
|
||||
scrolling
|
||||
multiple={ multiple }
|
||||
selectOnBlur={ false }
|
||||
// defaultValue={ value }
|
||||
icon={ icon ? <Icon name="chevron-down" color="gray-dark" size="14" className={stl.dropdownIcon} /> : null }
|
||||
|
|
|
|||
|
|
@ -100,18 +100,20 @@ const FilterDropdown = props => {
|
|||
</div>
|
||||
)}
|
||||
{showDropdown && (
|
||||
<div className="absolute mt-2 bg-white rounded border p-3 z-20" id="filter-dropdown" style={{ width: '200px'}}>
|
||||
<div className="font-medium mb-2 tracking-widest color-gray-dark">SELECT FILTER</div>
|
||||
{filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => (
|
||||
<div
|
||||
key={f.key}
|
||||
onClick={() => onFilterKeySelect(f.key)}
|
||||
className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')}
|
||||
>
|
||||
<Icon name={f.icon} size="16" />
|
||||
<span className="ml-3 capitalize">{f.name}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="absolute mt-2 bg-white rounded border z-20" id="filter-dropdown" style={{ width: '200px'}}>
|
||||
<div className="font-medium mb-2 tracking-widest color-gray-dark p-3">SELECT FILTER</div>
|
||||
<div className="px-3" style={{ maxHeight: '200px', overflowY: 'auto'}} >
|
||||
{filterKeys.filter(f => !filterKeyMaps.includes(f.key)).map(f => (
|
||||
<div
|
||||
key={f.key}
|
||||
onClick={() => onFilterKeySelect(f.key)}
|
||||
className={cn(stl.filterItem, 'py-3 -mx-3 px-3 flex items-center cursor-pointer')}
|
||||
>
|
||||
<Icon name={f.icon} size="16" />
|
||||
<span className="ml-3 capitalize">{f.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{filterKey && (
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ function FilterList(props: Props) {
|
|||
<div className="mr-2 color-gray-medium text-sm" style={{ textDecoration: 'underline dotted'}}>
|
||||
<Popup
|
||||
trigger={<div>Events Order</div>}
|
||||
content={ `Events Order` }
|
||||
content={ `Select the operator to be applied between events in your search.` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top center"
|
||||
|
|
|
|||
|
|
@ -9,13 +9,14 @@ class SegmentSelection extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { className, list, small = false, extraSmall = false, primary = false, size = "normal" } = this.props;
|
||||
const { className, list, small = false, extraSmall = false, primary = false, size = "normal", icons = false } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ cn(styles.wrapper, {
|
||||
[styles.primary] : primary,
|
||||
[styles.small] : size === 'small' || small,
|
||||
[styles.extraSmall] : size === 'extraSmall' || extraSmall,
|
||||
[styles.icons] : icons === true,
|
||||
}, className) }
|
||||
>
|
||||
{ list.map(item => (
|
||||
|
|
@ -27,7 +28,7 @@ class SegmentSelection extends React.Component {
|
|||
data-active={ this.props.value && this.props.value.value === item.value }
|
||||
onClick={ () => !item.disabled && this.setActiveItem(item) }
|
||||
>
|
||||
{ item.icon && <Icon name={ item.icon } size={size === "extraSmall" ? 12 : 20} marginRight={ item.name ? "10" : "" } /> }
|
||||
{ item.icon && <Icon name={ item.icon } size={(size === "extraSmall" || icons) ? 14 : 20} marginRight={ item.name ? "10" : "" } /> }
|
||||
<div>{ item.name }</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,15 @@
|
|||
color: $teal;
|
||||
background-color: white;
|
||||
border-right: solid thin $teal;
|
||||
& svg {
|
||||
fill: $teal !important;
|
||||
}
|
||||
&[data-active=true] {
|
||||
background-color: $teal;
|
||||
color: white;
|
||||
& svg {
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +72,11 @@
|
|||
}
|
||||
|
||||
.extraSmall .item {
|
||||
padding: 6px !important;
|
||||
padding: 0 4px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.icons .item {
|
||||
padding: 4px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
@ -69,6 +69,21 @@ export const metricOf = [
|
|||
{ text: 'URL', value: 'VISITED_URL', key: 'table' },
|
||||
]
|
||||
|
||||
export const issueOptions = [
|
||||
{ text: 'Click Rage', value: 'click_rage' },
|
||||
{ text: 'Dead Click', value: 'dead_click' },
|
||||
{ text: 'Excessive Scrolling', value: 'excessive_scrolling' },
|
||||
{ text: 'Bad Request', value: 'bad_request' },
|
||||
{ text: 'Missing Resource', value: 'missing_resource' },
|
||||
{ text: 'Memory', value: 'memory' },
|
||||
{ text: 'CPU', value: 'cpu' },
|
||||
{ text: 'Slow Resource', value: 'slow_resource' },
|
||||
{ text: 'Slow Page Load', value: 'slow_page_load' },
|
||||
{ text: 'Crash', value: 'crash' },
|
||||
{ text: 'Custom', value: 'custom' },
|
||||
{ text: 'JS Exception', value: 'js_exception' },
|
||||
]
|
||||
|
||||
export default {
|
||||
options,
|
||||
baseOperators,
|
||||
|
|
@ -79,4 +94,5 @@ export default {
|
|||
getOperatorsByKeys,
|
||||
metricTypes,
|
||||
metricOf,
|
||||
issueOptions,
|
||||
}
|
||||
3
frontend/app/svg/icons/table.svg
Normal file
3
frontend/app/svg/icons/table.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-table" viewBox="0 0 16 16">
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
|
|
@ -103,5 +103,11 @@ export default Record({
|
|||
endTimestamp: this.end,
|
||||
};
|
||||
},
|
||||
toTimestampstwo() {
|
||||
return {
|
||||
startTimestamp: this.start / 1000,
|
||||
endTimestamp: this.end / 1000,
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
@ -29,7 +29,9 @@ export default Record({
|
|||
name: 'Series',
|
||||
metricType: 'table',
|
||||
metricOf: 'USERID',
|
||||
viewType: 'lineChart',
|
||||
metricValue: ['sessionCount'],
|
||||
metricFormat: 'sessionCount',
|
||||
viewType: 'table',
|
||||
series: List(),
|
||||
isPublic: true,
|
||||
startDate: '',
|
||||
|
|
|
|||
|
|
@ -6,21 +6,6 @@ import { capitalize } from 'App/utils';
|
|||
const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i }));
|
||||
const containsFilters = [{ key: 'contains', text: 'contains', value: 'contains' }]
|
||||
|
||||
const ISSUE_OPTIONS = [
|
||||
{ text: 'Click Rage', value: 'click_rage' },
|
||||
{ text: 'Dead Click', value: 'dead_click' },
|
||||
{ text: 'Excessive Scrolling', value: 'excessive_scrolling' },
|
||||
{ text: 'Bad Request', value: 'bad_request' },
|
||||
{ text: 'Missing Resource', value: 'missing_resource' },
|
||||
{ text: 'Memory', value: 'memory' },
|
||||
{ text: 'CPU', value: 'cpu' },
|
||||
{ text: 'Slow Resource', value: 'slow_resource' },
|
||||
{ text: 'Slow Page Load', value: 'slow_page_load' },
|
||||
{ text: 'Crash', value: 'crash' },
|
||||
{ text: 'Custom', value: 'custom' },
|
||||
{ text: 'JS Exception', value: 'js_exception' },
|
||||
]
|
||||
|
||||
export const filtersMap = {
|
||||
// EVENTS
|
||||
[FilterKey.CLICK]: { key: FilterKey.CLICK, type: FilterType.MULTIPLE, category: FilterCategory.INTERACTIONS, label: 'Click', operator: 'on', operatorOptions: filterOptions.targetOperators, icon: 'filters/click', isEvent: true },
|
||||
|
|
@ -59,7 +44,7 @@ export const filtersMap = {
|
|||
[FilterKey.AVG_CPU_LOAD]: { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.AVG_MEMORY_USAGE]: { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.FETCH_FAILED]: { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch-failed', isEvent: true },
|
||||
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS },
|
||||
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: filterOptions.issueOptions },
|
||||
}
|
||||
|
||||
export const liveFiltersMap = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue