feat(ui) - custom metric widgets and preview api data changes
This commit is contained in:
parent
6d3f766787
commit
eb8559ec26
12 changed files with 146 additions and 73 deletions
|
|
@ -24,6 +24,7 @@ const siteIdRequiredPaths = [
|
|||
'/assist',
|
||||
'/heatmaps',
|
||||
'/custom_metrics',
|
||||
// '/custom_metrics/sessions',
|
||||
];
|
||||
|
||||
const noStoringFetchPathStarts = [
|
||||
|
|
|
|||
|
|
@ -1,27 +1,20 @@
|
|||
import { connect } from 'react-redux';
|
||||
// import { applyFilter } from 'Duck/filters';
|
||||
import { applyFilter } from 'Duck/search';
|
||||
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
|
||||
import DateRangeDropdown from 'Shared/DateRangeDropdown';
|
||||
|
||||
@connect(state => ({
|
||||
filter: state.getIn([ 'search', 'instance' ]),
|
||||
// rangeValue: state.getIn([ 'search', 'instance', 'rangeValue' ]),
|
||||
// startDate: state.getIn([ 'search', 'instance', 'startDate' ]),
|
||||
// endDate: state.getIn([ 'search', 'instance', 'endDate' ]),
|
||||
}), {
|
||||
applyFilter, fetchFunnelsList
|
||||
})
|
||||
export default class DateRange extends React.PureComponent {
|
||||
|
||||
onDateChange = (e) => {
|
||||
console.log('onDateChange', e);
|
||||
this.props.fetchFunnelsList(e.rangeValue)
|
||||
this.props.applyFilter(e)
|
||||
}
|
||||
render() {
|
||||
const { filter: { rangeValue, startDate, endDate }, className } = this.props;
|
||||
// const { startDate, endDate, rangeValue, className } = this.props;
|
||||
|
||||
return (
|
||||
<DateRangeDropdown
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { connect } from 'react-redux';
|
|||
import { Loader, NoContent, Icon } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LineChart, Line, Legend } 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';
|
||||
|
|
@ -40,7 +41,8 @@ interface Props {
|
|||
function CustomMetricWidget(props: Props) {
|
||||
const { metric, showSync, compare, period } = props;
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<any>({ chart: [] })
|
||||
const [data, setData] = useState<any>([]);
|
||||
const [seriesMap, setSeriesMap] = useState<any>([]);
|
||||
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
|
@ -54,8 +56,19 @@ function CustomMetricWidget(props: Props) {
|
|||
if (errors) {
|
||||
console.log('err', errors)
|
||||
} else {
|
||||
const _data = getChartFormatter(period)(data[0]);
|
||||
setData({ chart: _data });
|
||||
const namesMap = data
|
||||
.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
.reduce((unique: any, item: any) => {
|
||||
if (!unique.includes(item)) {
|
||||
unique.push(item);
|
||||
}
|
||||
return unique;
|
||||
}, []);
|
||||
|
||||
setSeriesMap(namesMap);
|
||||
setData(getChartFormatter(period)(data));
|
||||
}
|
||||
}).finally(() => setLoading(false));
|
||||
}, [period])
|
||||
|
|
@ -101,35 +114,52 @@ function CustomMetricWidget(props: Props) {
|
|||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ data.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
<LineChart
|
||||
data={ data }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "impactedSessionsBySlowPages" : undefined }
|
||||
syncId={ showSync ? "domainsErrors_4xx" : undefined }
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{gradientDef}
|
||||
<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
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Errors"
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Sessions"
|
||||
type="monotone"
|
||||
dataKey="count"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
{ 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}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ import { connect } from 'react-redux';
|
|||
import { Loader, NoContent, Icon } from 'UI';
|
||||
import { widgetHOC, Styles } from '../../common';
|
||||
import { ResponsiveContainer, AreaChart, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts';
|
||||
import { LineChart, Line, Legend } from 'recharts';
|
||||
import { 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';
|
||||
import { remove } from 'Duck/customMetrics';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import DateRange from 'Shared/DateRange';
|
||||
import { edit } from 'Duck/customMetrics';
|
||||
|
||||
import APIClient from 'App/api_client';
|
||||
|
||||
|
|
@ -35,11 +38,13 @@ interface Props {
|
|||
period?: Period;
|
||||
onClickEdit?: (e) => void;
|
||||
remove: (id) => void;
|
||||
edit: (metric) => void;
|
||||
}
|
||||
function CustomMetricWidget(props: Props) {
|
||||
const { metric, showSync, compare, period = { rangeName: LAST_24_HOURS} } = props;
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<any>({ chart: [{}] })
|
||||
const [seriesMap, setSeriesMap] = useState<any>([]);
|
||||
|
||||
const colors = compare ? Styles.compareColors : Styles.colors;
|
||||
const params = customParams(period.rangeName)
|
||||
|
|
@ -47,67 +52,106 @@ function CustomMetricWidget(props: Props) {
|
|||
const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' }
|
||||
|
||||
useEffect(() => {
|
||||
// new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toSaveData() })
|
||||
// .then(response => response.json())
|
||||
// .then(({ errors, data }) => {
|
||||
// if (errors) {
|
||||
// console.log('err', errors)
|
||||
// } else {
|
||||
// const _data = getChartFormatter(period)(data[0]);
|
||||
// // console.log('__data', _data)
|
||||
// setData({ chart: _data });
|
||||
// }
|
||||
// }).finally(() => setLoading(false));
|
||||
new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toSaveData() })
|
||||
.then(response => response.json())
|
||||
.then(({ errors, data }) => {
|
||||
if (errors) {
|
||||
console.log('err', errors)
|
||||
} else {
|
||||
const namesMap = data
|
||||
.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
.reduce((unique: any, item: any) => {
|
||||
if (!unique.includes(item)) {
|
||||
unique.push(item);
|
||||
}
|
||||
return unique;
|
||||
}, []);
|
||||
|
||||
setSeriesMap(namesMap);
|
||||
setData(getChartFormatter(period)(data));
|
||||
}
|
||||
}).finally(() => setLoading(false));
|
||||
}, [metric])
|
||||
|
||||
|
||||
const onDateChange = (changedDates) => {
|
||||
props.edit({ ...changedDates });
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-10">
|
||||
<div className="flex items-center mb-4">
|
||||
<div className="mr-auto font-medium">Preview</div>
|
||||
<div></div>
|
||||
<div>
|
||||
<DateRange
|
||||
rangeValue={metric.rangeValue}
|
||||
startDate={metric.startDate}
|
||||
endDate={metric.endDate}
|
||||
onDateChange={onDateChange}
|
||||
customRangeRight
|
||||
direction="left"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={stl.wrapper}>
|
||||
<div>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.chart.length === 0 }
|
||||
show={ data.length === 0 }
|
||||
>
|
||||
<ResponsiveContainer height={ 240 } width="100%">
|
||||
<AreaChart
|
||||
data={ data.chart }
|
||||
<LineChart
|
||||
data={ data }
|
||||
margin={Styles.chartMargins}
|
||||
syncId={ showSync ? "impactedSessionsBySlowPages" : undefined }
|
||||
syncId={ showSync ? "domainsErrors_4xx" : undefined }
|
||||
// onClick={clickHandler}
|
||||
>
|
||||
{gradientDef}
|
||||
<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
|
||||
<XAxis
|
||||
{...Styles.xaxis}
|
||||
dataKey="time"
|
||||
interval={params.density/7}
|
||||
/>
|
||||
<YAxis
|
||||
{...Styles.yaxis}
|
||||
label={{ ...Styles.axisLabelLeft, value: "Number of Requests" }}
|
||||
allowDecimals={false}
|
||||
label={{
|
||||
...Styles.axisLabelLeft,
|
||||
value: "Number of Errors"
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<Tooltip {...Styles.tooltip} />
|
||||
<Area
|
||||
name="Sessions"
|
||||
type="monotone"
|
||||
dataKey="count"
|
||||
stroke={colors[0]}
|
||||
fillOpacity={ 1 }
|
||||
strokeWidth={ 2 }
|
||||
strokeOpacity={ 0.8 }
|
||||
fill={compare ? 'url(#colorCountCompare)' : 'url(#colorCount)'}
|
||||
/>
|
||||
</AreaChart>
|
||||
{ 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}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, { remove })(CustomMetricWidget);
|
||||
export default connect(null, { remove, edit })(CustomMetricWidget);
|
||||
|
|
@ -53,7 +53,7 @@ function FilterSeries(props: Props) {
|
|||
|
||||
const onChangeEventsOrder = (e, { name, value }) => {
|
||||
props.updateSeries(seriesIndex, {
|
||||
...series.toData(),
|
||||
...series,
|
||||
filter: {
|
||||
...series.filter,
|
||||
eventsOrder: value,
|
||||
|
|
@ -79,7 +79,7 @@ function FilterSeries(props: Props) {
|
|||
<div className="border rounded bg-white">
|
||||
<div className="border-b px-5 h-12 flex items-center relative">
|
||||
<div className="mr-auto">
|
||||
<SeriesName name={series.name} onUpdate={() => null } />
|
||||
<SeriesName name={series.name} onUpdate={(name) => props.updateSeries(seriesIndex, { name }) } />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center cursor-pointer" >
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function SeriesName(props: Props) {
|
|||
|
||||
const onBlur = () => {
|
||||
setEditing(false)
|
||||
// props.onUpdate(name)
|
||||
props.onUpdate(name)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.wrapper {
|
||||
padding: 0 20px;
|
||||
padding: 20px;
|
||||
background-color: #f6f6f6;
|
||||
min-height: calc(100vh - 59px);
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ function SessionListModal(props: Props) {
|
|||
onClose={ () => props.setActiveWidget(null)}
|
||||
// size="medium"
|
||||
content={ activeWidget && (
|
||||
<div className="p-5">
|
||||
<div className="">
|
||||
<NoContent
|
||||
show={ !loading && (list.length === 0 || list.size === 0 )}
|
||||
title="No recordings found."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import DateRangeDropdown from 'Shared/DateRangeDropdown';
|
||||
|
||||
function DateRange (props) {
|
||||
const { startDate, endDate, rangeValue, className, onDateChange, customRangeRight=false, customHidden = false } = props;
|
||||
const { direction = "left", startDate, endDate, rangeValue, className, onDateChange, customRangeRight=false, customHidden = false } = props;
|
||||
|
||||
return (
|
||||
<DateRangeDropdown
|
||||
|
|
@ -14,6 +14,7 @@ function DateRange (props) {
|
|||
className={ className }
|
||||
customRangeRight={customRangeRight}
|
||||
customHidden={customHidden}
|
||||
direction={direction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ function reducer(state = initialState, action = {}) {
|
|||
case INIT:
|
||||
return state.set('instance', action.instance);
|
||||
case UPDATE_SERIES:
|
||||
console.log('update series', action.series);
|
||||
return state.mergeIn(['instance', 'series', action.index], action.series);
|
||||
case success(SAVE):
|
||||
return updateItemInList(updateInstance(state, action.data), action.data);
|
||||
|
|
@ -72,7 +73,7 @@ function reducer(state = initialState, action = {}) {
|
|||
const { data } = action;
|
||||
return state.set("list", List(data.map(CustomMetric)));
|
||||
case success(FETCH_SESSION_LIST):
|
||||
return state.set("sessionList", List(action.data).map(Session));
|
||||
return state.set("sessionList", List(action.data[0].sessions).map(Session));
|
||||
case SET_ACTIVE_WIDGET:
|
||||
return state.set("activeWidget", action.widget);
|
||||
}
|
||||
|
|
@ -151,7 +152,7 @@ export const init = (instnace = null, setDefault = true) => (dispatch, getState)
|
|||
export const fetchSessionList = (params) => (dispatch, getState) => {
|
||||
dispatch({
|
||||
types: array(FETCH_SESSION_LIST),
|
||||
call: client => client.post(`/sessions/search2`, { ...params }),
|
||||
call: client => client.post(`/custom_metrics/sessions`, { ...params }),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export default Record({
|
|||
type: 'session_count',
|
||||
series: List(),
|
||||
isPublic: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
}, {
|
||||
idKey: 'metricId',
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default Record({
|
|||
return js;
|
||||
}
|
||||
},
|
||||
fromJS({ filters, events, custom, ...filter }) {
|
||||
fromJS({ eventsOrder, filters, events, custom, ...filter }) {
|
||||
let startDate;
|
||||
let endDate;
|
||||
const rValue = filter.rangeValue || rangeValue;
|
||||
|
|
@ -91,6 +91,7 @@ export default Record({
|
|||
}
|
||||
return {
|
||||
...filter,
|
||||
eventsOrder,
|
||||
startDate,
|
||||
endDate,
|
||||
events: List(events).map(Event),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue