From 1d957f90b518b640e3a3692c80b4e12b759bec10 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 4 Mar 2022 16:25:35 +0100 Subject: [PATCH] feat(ui) - custom metrics - wip --- .../CustomMetricPieChart.tsx | 234 ++++++++---------- .../CustomMetricTable/CustomMetricTable.tsx | 39 ++- .../CustomMetricWidget/CustomMetricWidget.tsx | 16 +- .../CustomMetricWidgetPreview.css | 1 + .../Dashboard/Widgets/common/Table.js | 7 +- .../CustomMetricForm/CustomMetricForm.tsx | 17 +- .../SessionListModal/SessionListModal.tsx | 3 +- .../DateRangeDropdown/dateRangeDropdown.css | 8 +- .../shared/DropdownPlain/DropdownPlain.css | 4 +- frontend/app/constants/filterOptions.js | 16 +- frontend/app/types/customMetric.js | 9 +- 11 files changed, 194 insertions(+), 160 deletions(-) diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index 99c054fae..21f2d6d23 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -1,36 +1,8 @@ import React from 'react' -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Area, Tooltip } from 'recharts'; -import { LineChart, Line, Legend, PieChart, Pie, Cell } from 'recharts'; +import { ResponsiveContainer, Tooltip } from 'recharts'; +import { PieChart, Pie, Cell } from 'recharts'; import { Styles } from '../../common'; - - -function renderCustomizedLabel({ - cx, cy, midAngle, innerRadius, outerRadius, value, color, startAngle, endAngle}) { - const RADIAN = Math.PI / 180; - const diffAngle = endAngle - startAngle; - const delta = ((360-diffAngle)/15)-1; - const radius = innerRadius + (outerRadius - innerRadius); - const x = cx + (radius+delta) * Math.cos(-midAngle * RADIAN); - const y = cy + (radius+(delta*delta)) * Math.sin(-midAngle * RADIAN); - return ( - cx ? 'start' : 'end'} dominantBaseline="central" fontSize={12} fontWeight="normal"> - {value} - - ); -}; -function renderCustomizedLabelLine(props){ - let { cx, cy, midAngle, innerRadius, outerRadius, color, startAngle, endAngle } = props; - const RADIAN = Math.PI / 180; - const diffAngle = endAngle - startAngle; - const radius = 10 + innerRadius + (outerRadius - innerRadius); - let path=''; - for(let i=0;i<((360-diffAngle)/15);i++){ - path += `${(cx + (radius+i) * Math.cos(-midAngle * RADIAN))},${(cy + (radius+i*i) * Math.sin(-midAngle * RADIAN))} ` - } - return ( - - ); -} +import { NoContent } from 'UI'; interface Props { data: any; params: any; @@ -42,47 +14,22 @@ interface Props { function CustomMetricPieChart(props: Props) { const { data = { values: [] }, params, colors, onClick = () => null } = props; return ( - - - { - const RADIAN = Math.PI / 180; - let radius1 = 15 + innerRadius + (outerRadius - innerRadius); - let radius2 = innerRadius + (outerRadius - innerRadius); - let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN); - let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN); - let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN); - let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN); - - const percentage = value * 100 / data.values.reduce((a, b) => a + b.sessionCount, 0); - - if (percentage<3){ - return null; - } - - return( - - ) - }} - label={({ +
+ + + + { const RADIAN = Math.PI / 180; - let radius = 20 + innerRadius + (outerRadius - innerRadius); - let x = cx + radius * Math.cos(-midAngle * RADIAN); - let y = cy + radius * Math.sin(-midAngle * RADIAN); - const percentage = (value / data.values.reduce((a, b) => a + b.sessionCount, 0)) * 100; - if (percentage<3){ - return null; - } - return ( - cx ? "start" : "end"} - dominantBaseline="central" - fill='#3EAAAF' - > - {data.values[index].name} - ({value}) - - ); - }} - // label={({ - // cx, - // cy, - // midAngle, - // innerRadius, - // outerRadius, - // value, - // index - // }) => { - // const RADIAN = Math.PI / 180; - // const radius = 30 + innerRadius + (outerRadius - innerRadius); - // const x = cx + radius * Math.cos(-midAngle * RADIAN); - // const y = cy + radius * Math.sin(-midAngle * RADIAN); + let radius1 = 15 + innerRadius + (outerRadius - innerRadius); + let radius2 = innerRadius + (outerRadius - innerRadius); + let x2 = cx + radius1 * Math.cos(-midAngle * RADIAN); + let y2 = cy + radius1 * Math.sin(-midAngle * RADIAN); + let x1 = cx + radius2 * Math.cos(-midAngle * RADIAN); + let y1 = cy + radius2 * Math.sin(-midAngle * RADIAN); + + const percentage = value * 100 / data.values.reduce((a, b) => a + b.sessionCount, 0); + + if (percentage<3){ + return null; + } + + return( + + ) + }} + label={({ + cx, + cy, + midAngle, + innerRadius, + outerRadius, + value, + index + }) => { + const RADIAN = Math.PI / 180; + let radius = 20 + innerRadius + (outerRadius - innerRadius); + let x = cx + radius * Math.cos(-midAngle * RADIAN); + let y = cy + radius * Math.sin(-midAngle * RADIAN); + const percentage = (value / data.values.reduce((a, b) => a + b.sessionCount, 0)) * 100; + if (percentage<3){ + return null; + } + return ( + cx ? "start" : "end"} + dominantBaseline="central" + fill='#666' + > + {data.values[index].name} - ({value}) + + ); + }} + // label={({ + // cx, + // cy, + // midAngle, + // innerRadius, + // outerRadius, + // value, + // index + // }) => { + // const RADIAN = Math.PI / 180; + // const radius = 30 + innerRadius + (outerRadius - innerRadius); + // const x = cx + radius * Math.cos(-midAngle * RADIAN); + // const y = cy + radius * Math.sin(-midAngle * RADIAN); + + // return ( + // cx ? "start" : "end"} + // dominantBaseline="top" + // fontSize={10} + // > + // {data.values[index].name} ({value}) + // + // ); + // }} + > + {data.values.map((entry, index) => ( + + ))} + + + - // return ( - // cx ? "start" : "end"} - // dominantBaseline="top" - // fontSize={10} - // > - // {data.values[index].name} ({value}) - // - // ); - // }} - > - {data.values.map((entry, index) => ( - - ))} - - - - + +
Top 5
+
+
) } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx index be94acc57..d067b6666 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -1,6 +1,9 @@ import React from 'react' import { Table } from '../../common'; import { List } from 'immutable'; +import { FilterKey } from 'Types/filter/filterType'; +import { filtersMap } from 'Types/filter/newFilter'; +import { NoContent } from 'UI'; const cols = [ { @@ -18,20 +21,40 @@ const cols = [ ]; interface Props { + metric?: any, data: any; - onClick?: (event, index) => void; + onClick?: (filters) => void; } function CustomMetriTable(props: Props) { - const { data = { values: [] }, onClick = () => null } = props; + const { metric = {}, data = { values: [] }, onClick = () => null } = props; const rows = List(data.values); + + const onClickHandler = (event, data) => { + const filters = Array(); + let filter = { ...filtersMap[metric.metricOf] } + filter.value = [data.name] + filter.type = filter.key + delete filter.key + delete filter.operatorOptions + delete filter.category + delete filter.icon + delete filter.label + delete filter.options + + filters.push(filter); + onClick(filters); + } return (
- + +
+ ) } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index f473ed270..89e3416fa 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -76,6 +76,19 @@ function CustomMetricWidget(props: Props) { }).finally(() => setLoading(false)); }, [period]) + const clickHandlerTable = (filters) => { + const activeWidget = { + widget: metric, + period: period, + ...period.toTimestamps(), + filters, + // timestamp: payload.timestamp, + // index, + } + props.setActiveWidget(activeWidget); + // props.updateActiveState(metric.metricId, data); + } + const clickHandler = (event, index) => { if (event) { const payload = event.activePayload[0].payload; @@ -148,10 +161,11 @@ function CustomMetricWidget(props: Props) { {metric.viewType === 'table' && ( )} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css index 42444d934..2088330ba 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.css @@ -10,4 +10,5 @@ width: 70%; margin: 0 auto; background-color: white; + min-height: 220px; } \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/common/Table.js b/frontend/app/components/Dashboard/Widgets/common/Table.js index 8fcef4315..5e3c2fe0e 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Table.js +++ b/frontend/app/components/Dashboard/Widgets/common/Table.js @@ -18,6 +18,7 @@ export default class Table extends React.PureComponent { small = false, compare = false, maxHeight = 200, + onRowClick = () => {}, } = this.props; const { showAll } = this.state; @@ -33,7 +34,11 @@ export default class Table extends React.PureComponent {
{ rows.take(showAll ? 10 : (small ? 3 : 5)).map(row => ( -
+
onRowClick(e, row)} + > { cols.map(({ cellClass = '', className = '', Component, key, toText = t => t, width }) => (
{ Component ? diff --git a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx index 4ff533029..db6585654 100644 --- a/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx +++ b/frontend/app/components/shared/CustomMetrics/CustomMetricForm/CustomMetricForm.tsx @@ -9,6 +9,7 @@ import { toast } from 'react-toastify'; import cn from 'classnames'; import DropdownPlain from '../../DropdownPlain'; import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; +import { FilterKey } from 'Types/filter/filterType'; interface Props { metric: any; editMetric: (metric, shouldFetch?) => void; @@ -23,11 +24,11 @@ interface Props { function CustomMetricForm(props: Props) { const { metric, loading } = props; // 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'); + const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries'); + const tableOptions = metricOf.filter(i => i.type === 'table'); const isTable = metric.metricType === 'table'; const isTimeSeries = metric.metricType === 'timeseries'; - const _issueOptions = [{ text: 'All', value: '' }].concat(issueOptions); + const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions); const addSeries = () => { @@ -47,8 +48,8 @@ function CustomMetricForm(props: Props) { } if (name === 'metricOf') { - if (value === 'ISSUES') { - props.editMetric({ metricValue: [''] }, false); + if (value === FilterKey.ISSUE) { + props.editMetric({ metricValue: ['all'] }, false); } } @@ -139,7 +140,7 @@ function CustomMetricForm(props: Props) { )} - {metric.metricOf === 'ISSUES' && ( + {metric.metricOf === FilterKey.ISSUE && ( <> issue type removeSeries(index)} canDelete={metric.series.size > 1} + emptyMessage={isTable ? + 'Filter table data by user environment and metadata attributes. Use add step button below to filter.' : + 'Add user event or filter to define the series by clicking Add Step.' + } />
))} diff --git a/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx b/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx index bafa68fd1..d6434fadb 100644 --- a/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx +++ b/frontend/app/components/shared/CustomMetrics/SessionListModal/SessionListModal.tsx @@ -23,7 +23,8 @@ function SessionListModal(props: Props) { props.fetchSessionList({ metricId: activeWidget.widget.metricId, startDate: activeWidget.startTimestamp, - endDate: activeWidget.endTimestamp + endDate: activeWidget.endTimestamp, + filters: activeWidget.filters || [], }); }, [activeWidget]); diff --git a/frontend/app/components/shared/DateRangeDropdown/dateRangeDropdown.css b/frontend/app/components/shared/DateRangeDropdown/dateRangeDropdown.css index 27ae5b3bc..1cf17bf82 100644 --- a/frontend/app/components/shared/DateRangeDropdown/dateRangeDropdown.css +++ b/frontend/app/components/shared/DateRangeDropdown/dateRangeDropdown.css @@ -1,5 +1,5 @@ .button { - padding: 0 8px; + padding: 0 4px; border-radius: 3px; color: $teal; cursor: pointer; @@ -12,7 +12,7 @@ } .dropdownTrigger { - padding: 4px 6px; + padding: 4px; &:hover { background-color: $gray-light; } @@ -42,7 +42,7 @@ .dropdown { display: flex !important; - padding: 4px 6px; + padding: 4px 4px; border-radius: 3px; color: $gray-darkest; font-weight: 500; @@ -52,7 +52,7 @@ } .dropdownTrigger { - padding: 4px 8px; + padding: 4px 4px; border-radius: 3px; &:hover { background-color: $gray-light; diff --git a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css index 1bf3e305c..dd7b9a2a5 100644 --- a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css +++ b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css @@ -1,6 +1,6 @@ .dropdown { display: flex !important; - padding: 4px 6px; + padding: 4px; border-radius: 3px; color: $gray-darkest; font-weight: 500; @@ -12,7 +12,7 @@ } .dropdownTrigger { - padding: 4px 8px; + padding: 4px; border-radius: 3px; &:hover { background-color: $gray-light; diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index 6b9b5b70d..6fa6d0cbf 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -1,3 +1,5 @@ +import { FilterKey } from 'Types/filter/filterType'; + export const options = [ { key: 'on', text: 'on', value: 'on' }, { key: 'notOn', text: 'not on', value: 'notOn' }, @@ -60,13 +62,13 @@ export const metricTypes = [ ]; export const metricOf = [ - { text: 'Session Count', value: 'sessionCount', key: 'timeseries' }, - { text: 'Users', value: 'USERID', key: 'table' }, - { text: 'Issues', value: 'ISSUES', key: 'table' }, - { text: 'Browser', value: 'USERBROWSER', key: 'table' }, - { text: 'Device', value: 'USERDEVICE', key: 'table' }, - { text: 'Country', value: 'USERCOUNTRY', key: 'table' }, - { text: 'URL', value: 'VISITED_URL', key: 'table' }, + { text: 'Session Count', value: 'sessionCount', type: 'timeseries' }, + { text: 'Users', value: FilterKey.USERID, type: 'table' }, + { text: 'Issues', value: FilterKey.ISSUE, type: 'table' }, + { text: 'Browser', value: FilterKey.USER_BROWSER, type: 'table' }, + { text: 'Device', value: FilterKey.USER_DEVICE, type: 'table' }, + { text: 'Country', value: FilterKey.USER_COUNTRY, type: 'table' }, + { text: 'URL', value: FilterKey.LOCATION, type: 'table' }, ] export const issueOptions = [ diff --git a/frontend/app/types/customMetric.js b/frontend/app/types/customMetric.js index 0686af87d..0140faec4 100644 --- a/frontend/app/types/customMetric.js +++ b/frontend/app/types/customMetric.js @@ -3,6 +3,7 @@ import { List } from 'immutable'; import Filter from 'Types/filter'; import { validateName } from 'App/validate'; import { LAST_7_DAYS } from 'Types/app/period'; +import { FilterKey } from 'Types/filter/filterType'; import { filterMap } from 'Duck/search'; export const FilterSeries = Record({ @@ -47,11 +48,13 @@ export default Record({ toSaveData() { const js = this.toJS(); + + js.metricValue = js.metricValue.map(value => value === 'all' ? '' : value); js.series = js.series.map(series => { series.filter.filters = series.filter.filters.map(filterMap); // delete series._key - // delete series.key + delete series.key return series; }); @@ -65,8 +68,10 @@ export default Record({ return js; }, }, - fromJS: ({ series, ...rest }) => ({ + fromJS: ({ metricOf, metricValue, series, ...rest }) => ({ ...rest, series: List(series).map(FilterSeries), + metricOf, + metricValue: metricOf === FilterKey.ISSUE && metricValue.length === 0 ? ['all'] : metricValue, }), });