feat(ui) - custom metric widgets and preview api data changes

This commit is contained in:
Shekar Siri 2022-02-06 19:52:25 +01:00
parent 6d3f766787
commit eb8559ec26
12 changed files with 146 additions and 73 deletions

View file

@ -24,6 +24,7 @@ const siteIdRequiredPaths = [
'/assist',
'/heatmaps',
'/custom_metrics',
// '/custom_metrics/sessions',
];
const noStoringFetchPathStarts = [

View file

@ -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

View file

@ -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>

View file

@ -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);

View file

@ -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" >

View file

@ -17,7 +17,7 @@ function SeriesName(props: Props) {
const onBlur = () => {
setEditing(false)
// props.onUpdate(name)
props.onUpdate(name)
}
useEffect(() => {

View file

@ -1,5 +1,5 @@
.wrapper {
padding: 0 20px;
padding: 20px;
background-color: #f6f6f6;
min-height: calc(100vh - 59px);
}

View file

@ -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."

View file

@ -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}
/>
);
}

View file

@ -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 }),
});
}

View file

@ -28,6 +28,8 @@ export default Record({
type: 'session_count',
series: List(),
isPublic: false,
startDate: '',
endDate: '',
}, {
idKey: 'metricId',
methods: {

View file

@ -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),