feat(ui) - custom metrics

This commit is contained in:
Shekar Siri 2022-01-25 15:03:53 +05:30
parent 5f958ede98
commit 45db3fa1d4
13 changed files with 162 additions and 73 deletions

View file

@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { applyFilter } from 'Duck/filters';
// import { applyFilter } from 'Duck/filters';
import { applyFilter } from 'Duck/search';
import { fetchList as fetchFunnelsList } from 'Duck/funnels';
import DateRangeDropdown from 'Shared/DateRangeDropdown';
@ -12,6 +13,7 @@ import DateRangeDropdown from 'Shared/DateRangeDropdown';
})
export default class DateRange extends React.PureComponent {
onDateChange = (e) => {
console.log('onDateChange', e);
this.props.fetchFunnelsList(e.rangeValue)
this.props.applyFilter(e)
}

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { Dropdown } from 'semantic-ui-react';
import { Icon } from 'UI';
import { sort } from 'Duck/sessions';
import { applyFilter } from 'Duck/filters';
import { applyFilter } from 'Duck/search';
import stl from './sortDropdown.css';
@connect(null, { sort, applyFilter })

View file

@ -99,9 +99,11 @@ function FilterSeries(props: Props) {
<div className="p-5">
{ series.filter.filters.size > 0 ? (
<FilterList
filters={series.filter.filters.toJS()}
// filters={series.filter.filters.toJS()}
filter={series.filter}
onUpdateFilter={onUpdateFilter}
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
/>
): (
<div className="color-gray-medium">Add user event or filter to build the series.</div>

View file

@ -9,9 +9,10 @@ interface Props {
filter: any; // event/filter
onUpdate: (filter) => void;
onRemoveFilter: () => void;
isFilter?: boolean;
}
function FitlerItem(props: Props) {
const { filterIndex, filter, onUpdate } = props;
const { isFilter = false, filterIndex, filter, onUpdate } = props;
const replaceFilter = (filter) => {
onUpdate(filter);
@ -45,17 +46,17 @@ function FitlerItem(props: Props) {
return (
<div className="flex items-center mb-4">
<div className="flex items-start mr-auto">
<div className="mt-1 w-6 h-6 text-xs flex justify-center rounded-full bg-gray-light-shade mr-2">{filterIndex+1}</div>
{ !isFilter && <div className="mt-1 w-6 h-6 text-xs flex justify-center rounded-full bg-gray-light-shade mr-2">{filterIndex+1}</div> }
<FilterSelection filter={filter} onFilterClick={replaceFilter} />
<FilterOperator filter={filter} onChange={onOperatorChange} className="mx-2 flex-shrink-0"/>
<FilterValue filter={filter} onUpdate={onUpdate} />
</div>
<div className="flex self-start mt-2">
<div
className="cursor-pointer"
className="cursor-pointer p-1"
onClick={props.onRemoveFilter}
>
<Icon name="close" size="18" />
<Icon name="trash" size="16" />
</div>
</div>
</div>

View file

@ -3,12 +3,17 @@ import FilterItem from '../FilterItem';
import { SegmentSelection } from 'UI';
interface Props {
filters: any[]; // event/filter
// filters: any[]; // event/filter
filter?: any; // event/filter
onUpdateFilter: (filterIndex, filter) => void;
onRemoveFilter: (filterIndex) => void;
onChangeEventsOrder: (e, { name, value }) => void;
}
function FilterList(props: Props) {
const { filters } = props;
const { filter } = props;
const filters = filter.filters.toJS()
const hasEvents = filter.filters.filter(i => i.isEvent).size > 0;
const hasFilters = filter.filters.filter(i => !i.isEvent).size > 0;
const onRemoveFilter = (filterIndex) => {
const newFilters = filters.filter((_filter, i) => {
@ -20,45 +25,56 @@ function FilterList(props: Props) {
return (
<div className="flex flex-col">
<div className="flex items-center mb-2">
<div className="mb-2 text-sm color-gray-medium mr-auto">EVENTS</div>
<div className="flex items-center">
<div className="mr-2 color-gray-medium text-sm">Events Order</div>
<SegmentSelection
primary
name="eventsOrder"
extraSmall={true}
// className="my-3"
// onSelect={onChangeEventsOrder }
onSelect={() => null }
// value={{ value: series.filter.eventsOrder }}
value={{ value: 'and' }}
list={ [
{ name: 'AND', value: 'and' },
{ name: 'OR', value: 'or' },
{ name: 'THEN', value: 'then' },
]}
/>
</div>
</div>
{filters.map((filter, filterIndex) => (
<FilterItem
filterIndex={filterIndex}
filter={filter}
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
onRemoveFilter={() => onRemoveFilter(filterIndex) }
/>
))}
{ hasEvents && (
<>
<div className="flex items-center mb-2">
<div className="mb-2 text-sm color-gray-medium mr-auto">EVENTS</div>
<div className="flex items-center">
<div className="mr-2 color-gray-medium text-sm">Events Order</div>
<SegmentSelection
primary
name="eventsOrder"
extraSmall={true}
// className="my-3"
onSelect={props.onChangeEventsOrder}
// onSelect={() => null }
value={{ value: filter.eventsOrder }}
// value={{ value: 'and' }}
list={ [
{ name: 'AND', value: 'and' },
{ name: 'OR', value: 'or' },
{ name: 'THEN', value: 'then' },
]}
/>
</div>
</div>
{filters.map((filter, filterIndex) => filter.isEvent ? (
<FilterItem
filterIndex={filterIndex}
filter={filter}
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
onRemoveFilter={() => onRemoveFilter(filterIndex) }
/>
): null)}
<div className='mb-2' />
</>
)}
{/* <div>Filters</div>
{filters.filter(f => !f.isEvent).map((filter, filterIndex) => (
<FilterItem
filterIndex={filter.index}
filter={filter}
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
onRemoveFilter={() => onRemoveFilter(filterIndex) }
/>
))} */}
{hasFilters && (
<>
<div className='border-t -mx-5 mb-2' />
<div className="mb-2 text-sm color-gray-medium mr-auto">FILTERS</div>
{filters.map((filter, filterIndex) => !filter.isEvent ? (
<FilterItem
isFilter={true}
filterIndex={filterIndex}
filter={filter}
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
onRemoveFilter={() => onRemoveFilter(filterIndex) }
/>
): null)}
</>
)}
</div>
);
}

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import FilterAutoComplete from '../FilterAutoComplete';
import { FilterType } from 'Types/filter/filterType';
import FilterValueDropdown from '../FilterValueDropdown';
@ -10,6 +10,7 @@ interface Props {
}
function FilterValue(props: Props) {
const { filter } = props;
const [durationValues, setDurationValues] = useState({ minDuration: 0, maxDuration: 0 });
const onAddValue = () => {
const newValues = filter.value.concat("")
@ -31,6 +32,25 @@ function FilterValue(props: Props) {
props.onUpdate({ ...filter, value: newValues })
}
const onDurationChange = (newValues) => {
console.log('durationValues', durationValues)
// setDurationValues({ ...durationValues });
setDurationValues({ ...durationValues, ...newValues });
}
const handleBlur = (e) => {
// const { filter, onChange } = props;
if (filter.type === FilterType.DURATION) {
const { maxDuration, minDuration, key } = filter;
if (maxDuration || minDuration) return;
if (maxDuration !== durationValues.maxDuration ||
minDuration !== durationValues.minDuration) {
// onChange(e, { name: 'value', value: [this.state.minDuration, this.state.maxDuration] });
props.onUpdate({ ...filter, value: [durationValues.minDuration, durationValues.maxDuration] });
}
}
}
const renderValueFiled = (value, valueIndex) => {
switch(filter.type) {
case FilterType.ISSUE:
@ -43,12 +63,22 @@ function FilterValue(props: Props) {
onChange={(e, { name, value }) => onSelect(e, { value }, valueIndex)}
/>
)
case FilterType.MULTIPLE_DROPDOWN:
return (
<FilterValueDropdown
multiple={true}
value={value}
filter={filter}
options={filter.options}
onChange={(e, { name, value }) => onSelect(e, { value }, valueIndex)}
/>
)
case FilterType.DURATION:
return (
<FilterDuration
// onChange={ this.onDurationChange }
onChange={ onDurationChange }
// onEnterPress={ this.handleClose }
// onBlur={this.handleClose}
onBlur={handleBlur}
minDuration={ filter.value[0] }
maxDuration={ filter.value[1] }
/>
@ -81,12 +111,17 @@ function FilterValue(props: Props) {
)
}
}
console.log('durationValues', durationValues)
return (
<div className="grid grid-cols-3 gap-3">
{filter.value && filter.value.map((value, valueIndex) => (
renderValueFiled(value, valueIndex)
))}
{ filter.type === FilterType.DURATION ? (
renderValueFiled(filter.value, 0)
) : (
filter.value && filter.value.map((value, valueIndex) => (
renderValueFiled(value, valueIndex)
))
)}
</div>
);
}

View file

@ -11,9 +11,10 @@ interface Props {
className?: string;
options: any[];
search?: boolean;
multiple?: boolean;
}
function FilterValueDropdown(props: Props) {
const { search = false, options, onChange, value, className = '' } = props;
const { multiple = false, search = false, options, onChange, value, className = '' } = props;
// const options = []
return (

View file

@ -57,17 +57,40 @@ function SessionSearch(props) {
});
}
const onChangeEventsOrder = (e, { name, value }) => {
props.edit({
...appliedFilter.toData(),
filter: {
...appliedFilter.filter.toData(),
eventsOrder: value,
}
});
}
const clearSearch = () => {
props.edit({
...appliedFilter.toData(),
filter: {
...appliedFilter.filter.toData(),
filters: [],
}
});
}
return (
<div className="border bg-white rounded mt-4">
<div className="p-3">
<div className="p-5">
<FilterList
filters={appliedFilter.filter.filters.toJS()}
// filters={appliedFilter.filter.filters.toJS()}
filter={appliedFilter.filter}
onUpdateFilter={onUpdateFilter}
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
/>
</div>
<div className="border-t px-3 py-2 flex items-center">
<div className="border-t px-5 py-1 flex items-center -mx-2">
<div>
<FilterSelection
filter={undefined}
@ -78,7 +101,7 @@ function SessionSearch(props) {
</div>
<div className="ml-auto flex items-center">
<SaveFilterButton />
<Button>CLEAR STEPS</Button>
<Button onClick={clearSearch}>CLEAR STEPS</Button>
<Button plain>SAVE FUNNEL</Button>
</div>
</div>

View file

@ -10,6 +10,7 @@ export { default as alertConditions } from './alertConditions';
export { default as alertMetrics } from './alertMetrics';
export { default as regions } from './regions';
export { default as links } from './links';
export { default as platformOptions } from './platformOptions';
export {
DAYS as SCHEDULE_DAYS,
HOURS as SCHEDULE_HOURS,

View file

@ -0,0 +1,5 @@
export default [
{ value: 'desktop', text: 'Desktop' },
{ value: 'mobile', text: 'Mobile' },
{ value: 'tablet', text: 'Tablet' },
]

View file

@ -26,6 +26,7 @@ const SAVE = saveType(name);
const EDIT = editType(name);
const REMOVE = removeType(name);
const UPDATE = `${name}/UPDATE`;
const APPLY = `${name}/APPLY`;
const SET_ALERT_METRIC_ID = `${name}/SET_ALERT_METRIC_ID`;
function chartWrapper(chart = []) {
@ -49,6 +50,13 @@ function reducer(state = initialState, action = {}) {
switch (action.type) {
case EDIT:
return state.set('instance', FilterSeries(action.instance));
case APPLY:
return action.fromUrl
? state.set('instance',
Filter(action.filter)
// .set('events', state.getIn([ 'instance', 'events' ]))
)
: state.mergeIn([ 'instance', 'filter' ], action.filter);
case success(SAVE):
return state.set([ 'instance' ], CustomMetric(action.data));
case success(REMOVE):
@ -85,13 +93,7 @@ const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getStat
dispatch(actionCreator(...args));
const filter = getState().getIn([ 'search', 'instance', 'filter' ]).toData();
filter.filters = filter.filters.map(filterMap);
// console.log('filter', filter)
// let filter = appliedFilter
// .update('filters', list => list.map(f => f.set('value', f.value || '*'))
// .map(filterMap));
// const filter.filters = getState().getIn([ 'instance', 'filter' ]).get('filters').map(filterMap).toJS();
filter.isNew = true // TODO remove this line
return isRoute(ERRORS_ROUTE, window.location.pathname)
? dispatch(fetchErrorsList(filter))
@ -105,11 +107,11 @@ export const edit = reduceThenFetchResource((instance) => ({
export const remove = createRemove(name);
// export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
// type: APPLY,
// filter,
// fromUrl,
// }));
export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
type: APPLY,
filter,
fromUrl,
}));
export const updateSeries = (index, series) => ({
type: UPDATE,

View file

@ -6,6 +6,7 @@ export enum FilterType {
MULTIPLE = "MULTIPLE",
COUNTRY = "COUNTRY",
DROPDOWN = "DROPDOWN",
MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN",
};
export enum FilterKey {

View file

@ -1,6 +1,6 @@
import Record from 'Types/Record';
import { FilterType, FilterKey } from './filterType'
import { countries } from 'App/constants';
import { countries, platformOptions } from 'App/constants';
const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i }));
@ -194,11 +194,11 @@ export const filtersMap = {
[FilterKey.USER_OS]: { key: FilterKey.USER_OS, type: FilterType.MULTIPLE, category: 'gear', label: 'User OS', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/os' },
[FilterKey.USER_BROWSER]: { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: 'gear', label: 'User Browser', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/browser' },
[FilterKey.USER_DEVICE]: { key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: 'gear', label: 'User Device', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/device' },
[FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE, category: 'gear', label: 'Platform', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/platform' },
[FilterKey.PLATFORM]: { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: 'gear', label: 'Platform', operator: 'is', operatorOptions: filterOptions, icon: 'filters/platform', options: platformOptions },
[FilterKey.REVID]: { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: 'gear', label: 'RevId', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/rev-id' },
[FilterKey.REFERRER]: { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: 'recording_attributes', label: 'Referrer', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/referrer' },
[FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.NUMBER, category: 'recording_attributes', label: 'Duration', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/duration' },
[FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: 'recording_attributes', label: 'Duration', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/duration' },
[FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.DROPDOWN, category: 'recording_attributes', label: 'User Country', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/country', options: countryOptions },
[FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: 'javascript', label: 'Console', operator: 'is', operatorOptions: stringFilterOptions, icon: 'filters/console' },