feat(ui) - network request
This commit is contained in:
parent
1de8f42dd2
commit
755d947775
12 changed files with 113 additions and 55 deletions
|
|
@ -33,9 +33,6 @@ function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) {
|
|||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<CustomMetrics />
|
||||
<div className="color-gray-medium mt-2">
|
||||
Be proactive by monitoring the metrics you care about the most.
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function CustomMetricPieChart(props: Props) {
|
|||
const { metric, data = { values: [] }, onClick = () => null } = props;
|
||||
|
||||
const onClickHandler = (event) => {
|
||||
if (event) {
|
||||
if (event && !event.payload.group) {
|
||||
const filters = Array<any>();
|
||||
let filter = { ...filtersMap[metric.metricOf] }
|
||||
filter.value = [event.payload.name]
|
||||
|
|
@ -91,6 +91,8 @@ function CustomMetricPieChart(props: Props) {
|
|||
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;
|
||||
let name = data.values[index].name || 'Unidentified';
|
||||
name = name.length > 20 ? name.substring(0, 20) + '...' : name;
|
||||
if (percentage<3){
|
||||
return null;
|
||||
}
|
||||
|
|
@ -105,7 +107,7 @@ function CustomMetricPieChart(props: Props) {
|
|||
dominantBaseline="central"
|
||||
fill='#666'
|
||||
>
|
||||
{data.values[index].name} - ({value})
|
||||
{name || 'Unidentified'} {value}
|
||||
</text>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
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';
|
||||
import { tableColumnName } from 'App/constants/filterOptions';
|
||||
|
||||
const cols = [
|
||||
{
|
||||
key: 'name',
|
||||
title: 'Resource',
|
||||
toText: name => name || 'Unidentified',
|
||||
width: '70%',
|
||||
},
|
||||
{
|
||||
key: 'sessionCount',
|
||||
title: 'Sessions',
|
||||
toText: sessions => sessions,
|
||||
width: '30%',
|
||||
},
|
||||
];
|
||||
const getColumns = (metric) => {
|
||||
return [
|
||||
{
|
||||
key: 'name',
|
||||
title: tableColumnName[metric.metricOf],
|
||||
toText: name => name || 'Unidentified',
|
||||
width: '70%',
|
||||
},
|
||||
{
|
||||
key: 'sessionCount',
|
||||
title: 'Sessions',
|
||||
toText: sessions => sessions,
|
||||
width: '30%',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
metric?: any,
|
||||
|
|
@ -49,7 +51,7 @@ function CustomMetriTable(props: Props) {
|
|||
<NoContent show={data.values && data.values.length === 0} size="small">
|
||||
<Table
|
||||
small
|
||||
cols={ cols }
|
||||
cols={ getColumns(metric) }
|
||||
rows={ rows }
|
||||
rowClass="group"
|
||||
onRowClick={ onClickHandler }
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ function CustomMetricWidget(props: Props) {
|
|||
<div className="flex items-center">
|
||||
{isTimeSeries && (
|
||||
<>
|
||||
<span className="mr-1 color-gray-medium">Visualization</span>
|
||||
<span className="color-gray-medium mr-2">Visualization</span>
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ function CustomMetricForm(props: Props) {
|
|||
onRemoveSeries={() => 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.' :
|
||||
'Filter data using any event or attribute. Use Add Step button below to do so.' :
|
||||
'Add user event or filter to define the series by clicking Add Step.'
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ interface Props {
|
|||
onSelect: (e, item) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
type?: string;
|
||||
isMultilple?: boolean;
|
||||
}
|
||||
|
||||
function FilterAutoCompleteLocal(props: Props) {
|
||||
|
|
@ -24,6 +26,8 @@ function FilterAutoCompleteLocal(props: Props) {
|
|||
onAddValue = () => null,
|
||||
value = '',
|
||||
icon = null,
|
||||
type = "text",
|
||||
isMultilple = true,
|
||||
} = props;
|
||||
const [showModal, setShowModal] = useState(true)
|
||||
const [query, setQuery] = useState(value);
|
||||
|
|
@ -59,7 +63,7 @@ function FilterAutoCompleteLocal(props: Props) {
|
|||
onFocus={ () => setShowModal(true)}
|
||||
value={ query }
|
||||
autoFocus={ true }
|
||||
type="text"
|
||||
type={ type }
|
||||
placeholder={ placeholder }
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
|
|
@ -71,7 +75,7 @@ function FilterAutoCompleteLocal(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{ !showOrButton && <div className="ml-3">or</div> }
|
||||
{ !showOrButton && isMultilple && <div className="ml-3">or</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function FilterValue(props: Props) {
|
|||
}
|
||||
|
||||
const renderValueFiled = (value, valueIndex) => {
|
||||
const showOrButton = valueIndex === lastIndex;
|
||||
const showOrButton = valueIndex === lastIndex && filter.type !== FilterType.NUMBER;
|
||||
switch(filter.type) {
|
||||
case FilterType.STRING:
|
||||
return (
|
||||
|
|
@ -113,17 +113,41 @@ function FilterValue(props: Props) {
|
|||
maxDuration={ durationValues.maxDuration }
|
||||
/>
|
||||
)
|
||||
case FilterType.NUMBER:
|
||||
case FilterType.NUMBER_MULTIPLE:
|
||||
return (
|
||||
<input
|
||||
className="w-full px-2 py-1 text-sm leading-tight text-gray-700 rounded-lg"
|
||||
type="number"
|
||||
name={`${filter.key}-${valueIndex}`}
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
onChange={(e) => onChange(e, { value: e.target.value }, valueIndex)}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
/>
|
||||
)
|
||||
case FilterType.NUMBER:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
isMultilple={false}
|
||||
/>
|
||||
// <input
|
||||
// className="w-full px-2 py-1 text-sm leading-tight text-gray-700 rounded bg-white border"
|
||||
// type="number"
|
||||
// name={`${filter.key}-${valueIndex}`}
|
||||
// value={value}
|
||||
// placeholder="Enter"
|
||||
// onChange={(e) => onChange(e, { value: e.target.value }, valueIndex)}
|
||||
// />
|
||||
)
|
||||
case FilterType.MULTIPLE:
|
||||
return (
|
||||
<FilterAutoComplete
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
width: 100%;
|
||||
|
||||
& .right {
|
||||
height: 24px;
|
||||
|
|
|
|||
|
|
@ -16,31 +16,36 @@ interface Props {
|
|||
showOrButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
isMultilple?: boolean;
|
||||
}
|
||||
function FilterValueDropdown(props: Props) {
|
||||
const { filter, multiple = false, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props;
|
||||
const { filter, multiple = false, isMultilple = true, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props;
|
||||
// const options = []
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<Dropdown
|
||||
search={search}
|
||||
className={ cn(stl.operatorDropdown, className, "filterDropdown") }
|
||||
options={ options }
|
||||
name="issue_type"
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
placeholder="Select"
|
||||
fluid
|
||||
icon={ <Icon className="absolute right-0 mr-2" name="chevron-down" size="12" /> }
|
||||
/>
|
||||
<div
|
||||
className={stl.right}
|
||||
// onClick={showOrButton ? onRemoveValue : onAddValue}
|
||||
>
|
||||
{ showCloseButton && <div onClick={props.onRemoveValue}><Icon name="close" size="12" /></div> }
|
||||
{ showOrButton && <div onClick={props.onAddValue} className="color-teal"><span className="px-1">or</span></div> }
|
||||
<div className="relative flex items-center w-full">
|
||||
<div className={stl.wrapper}>
|
||||
<Dropdown
|
||||
search={search}
|
||||
className={ cn(stl.operatorDropdown, className, "filterDropdown") }
|
||||
options={ options }
|
||||
name="issue_type"
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
placeholder="Select"
|
||||
fluid
|
||||
icon={ <Icon className="absolute right-0 mr-2" name="chevron-down" size="12" /> }
|
||||
/>
|
||||
<div
|
||||
className={stl.right}
|
||||
// onClick={showOrButton ? onRemoveValue : onAddValue}
|
||||
>
|
||||
{ showCloseButton && <div onClick={props.onRemoveValue}><Icon name="close" size="12" /></div> }
|
||||
{ showOrButton && <div onClick={props.onAddValue} className="color-teal"><span className="px-1">or</span></div> }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ !showOrButton && isMultilple && <div className="ml-3">or</div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,15 @@ export const metricTypes = [
|
|||
{ text: 'Table', value: 'table' },
|
||||
];
|
||||
|
||||
export const tableColumnName = {
|
||||
[FilterKey.USERID]: 'User',
|
||||
[FilterKey.ISSUE]: 'Issue',
|
||||
[FilterKey.USER_BROWSER]: 'Browser',
|
||||
[FilterKey.USER_DEVICE]: 'Device',
|
||||
[FilterKey.USER_COUNTRY]: 'Country',
|
||||
[FilterKey.LOCATION]: 'URL',
|
||||
}
|
||||
|
||||
export const metricOf = [
|
||||
{ text: 'Session Count', value: 'sessionCount', type: 'timeseries' },
|
||||
{ text: 'Users', value: FilterKey.USERID, type: 'table' },
|
||||
|
|
@ -71,6 +80,18 @@ export const metricOf = [
|
|||
{ text: 'URL', value: FilterKey.LOCATION, type: 'table' },
|
||||
]
|
||||
|
||||
export const methodOptions = [
|
||||
{ text: 'GET', value: 'GET' },
|
||||
{ text: 'POST', value: 'POST' },
|
||||
{ text: 'PUT', value: 'PUT' },
|
||||
{ text: 'DELETE', value: 'DELETE' },
|
||||
{ text: 'PATCH', value: 'PATCH' },
|
||||
{ text: 'HEAD', value: 'HEAD' },
|
||||
{ text: 'OPTIONS', value: 'OPTIONS' },
|
||||
{ text: 'TRACE', value: 'TRACE' },
|
||||
{ text: 'CONNECT', value: 'CONNECT' },
|
||||
]
|
||||
|
||||
export const issueOptions = [
|
||||
{ text: 'Click Rage', value: 'click_rage' },
|
||||
{ text: 'Dead Click', value: 'dead_click' },
|
||||
|
|
@ -97,4 +118,5 @@ export default {
|
|||
metricTypes,
|
||||
metricOf,
|
||||
issueOptions,
|
||||
methodOptions,
|
||||
}
|
||||
|
|
@ -107,14 +107,15 @@ export const checkFilterValue = (value) => {
|
|||
return Array.isArray(value) ? (value.length === 0 ? [""] : value) : [value];
|
||||
}
|
||||
|
||||
export const filterMap = ({category, value, key, operator, sourceOperator, source, custom, isEvent }) => ({
|
||||
export const filterMap = ({category, value, key, operator, sourceOperator, source, custom, isEvent, subFilters }) => ({
|
||||
value: checkValues(key, value),
|
||||
custom,
|
||||
type: category === FilterCategory.METADATA ? FilterKey.METADATA : key,
|
||||
operator,
|
||||
source: category === FilterCategory.METADATA ? key : source,
|
||||
sourceOperator,
|
||||
isEvent
|
||||
isEvent,
|
||||
filters: subFilters ? subFilters.map(filterMap) : [],
|
||||
});
|
||||
|
||||
const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ export const filtersMap = {
|
|||
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
|
||||
// PERFORMANCE
|
||||
[FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.PERFORMANCE, label: 'Fetch Request', subFilters: [
|
||||
[FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.PERFORMANCE, operator: 'is', label: 'Network Request', subFilters: [
|
||||
{ key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_STATUS_CODE, type: FilterType.NUMBER_MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch', options: filterOptions.methodOptions },
|
||||
{ key: FilterKey.FETCH_DURATION, type: FilterType.NUMBER, category: FilterCategory.PERFORMANCE, label: 'with duration', operator: '=', operatorOptions: filterOptions.customOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_REQUEST_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with request body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue