feat(ui) - filters - metadata and other ui changes
This commit is contained in:
parent
7d3103071e
commit
a55b208434
14 changed files with 111 additions and 80 deletions
|
|
@ -54,7 +54,7 @@ function CustomMetricWidget(props: Props) {
|
|||
console.log('err', errors)
|
||||
} else {
|
||||
const _data = getChartFormatter(period)(data[0]);
|
||||
console.log('__data', _data)
|
||||
// console.log('__data', _data)
|
||||
setData({ chart: _data });
|
||||
}
|
||||
}).finally(() => setLoading(false));
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ interface Props {
|
|||
save: (metric) => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
function CustomMetricForm(props: Props) {
|
||||
const { metric, loading } = props;
|
||||
|
||||
|
|
@ -18,15 +19,16 @@ function CustomMetricForm(props: Props) {
|
|||
const newSeries = {
|
||||
name: `Series ${metric.series.size + 1}`,
|
||||
type: '',
|
||||
series: [],
|
||||
// series: [],
|
||||
filter: {
|
||||
type: '',
|
||||
value: '',
|
||||
filters: [],
|
||||
},
|
||||
};
|
||||
props.editMetric({
|
||||
...metric,
|
||||
series: [...metric.series, newSeries],
|
||||
series: metric.series.concat(newSeries),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -40,10 +42,10 @@ function CustomMetricForm(props: Props) {
|
|||
});
|
||||
}
|
||||
|
||||
const write = ({ target: { value, name } }) => props.editMetric({ ...metric.toData(), [ name ]: value })
|
||||
const write = ({ target: { value, name } }) => props.editMetric({ ...metric, [ name ]: value })
|
||||
|
||||
const changeConditionTab = (e, { name, value }) => {
|
||||
props.editMetric({ ...metric.toData(), [ 'type' ]: value })
|
||||
props.editMetric({ ...metric, [ 'type' ]: value })
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import stl from './FilterModal.css';
|
||||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import { filtersMap, getMetaDataFilter } from 'Types/filter/newFilter';
|
||||
import { FilterKey, FilterType } from 'Types/filter/filterType';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -11,21 +11,31 @@ interface Props {
|
|||
onFilterClick?: (filter) => void,
|
||||
filterSearchList: any,
|
||||
metaOptions: any,
|
||||
isMainSearch?: boolean,
|
||||
fetchingFilterSearchList: boolean,
|
||||
searchQuery?: string,
|
||||
}
|
||||
function FilterModal(props: Props) {
|
||||
const { filters, metaOptions, onFilterClick = () => null, filterSearchList } = props;
|
||||
const {
|
||||
filters,
|
||||
metaOptions,
|
||||
onFilterClick = () => null,
|
||||
filterSearchList,
|
||||
isMainSearch = false,
|
||||
fetchingFilterSearchList,
|
||||
searchQuery = '',
|
||||
} = props;
|
||||
const hasFilerSearchList = filterSearchList && Object.keys(filterSearchList).length > 0;
|
||||
const hasSearchQuery = searchQuery && searchQuery.length > 0;
|
||||
const showSearchList = isMainSearch && searchQuery.length > 0;
|
||||
|
||||
const allFilters = Object.assign({}, filtersMap);
|
||||
const allFilters = Object.assign({}, filters);
|
||||
if (metaOptions.size > 0) {
|
||||
allFilters['Metadata'] = [];
|
||||
metaOptions.forEach((option) => {
|
||||
if (option.key) {
|
||||
allFilters[option.key] = {
|
||||
category: FilterKey.METADATA,
|
||||
key: option.key,
|
||||
name: option.key,
|
||||
label: option.key,
|
||||
};
|
||||
const _metaFilter = getMetaDataFilter(option.key, option.value);
|
||||
allFilters['Metadata'].push(_metaFilter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -36,51 +46,55 @@ function FilterModal(props: Props) {
|
|||
onFilterClick(_filter);
|
||||
}
|
||||
return (
|
||||
<div className={stl.wrapper} style={{ width: '490px', height: '400px', overflowY: 'auto'}}>
|
||||
{ hasFilerSearchList && (
|
||||
<div className="border-b -mx-6 px-6 mb-3">
|
||||
{ filterSearchList && Object.keys(filterSearchList).map((key, index) => {
|
||||
const filter = filterSearchList[key];
|
||||
const option = filtersMap[key];
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={cn('mb-3')}
|
||||
>
|
||||
<div className="font-medium uppercase color-gray-medium text-sm mb-2">{option.label}</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 text-sm flex items-center")}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
<div className="whitespace-nowrap text-ellipsis overflow-hidden">{f.value}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className={stl.wrapper} style={{ width: '490px', maxHeight: '400px', overflowY: 'auto'}}>
|
||||
{ showSearchList && (
|
||||
<Loader size="small" loading={fetchingFilterSearchList}>
|
||||
<div className="-mx-6 px-6">
|
||||
{ filterSearchList && Object.keys(filterSearchList).map((key, index) => {
|
||||
const filter = filterSearchList[key];
|
||||
const option = filtersMap[key];
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={cn('mb-3')}
|
||||
>
|
||||
<div className="font-medium uppercase color-gray-medium text-sm mb-2">{option.label}</div>
|
||||
<div>
|
||||
{filter.map((f, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(stl.filterSearchItem, "cursor-pointer px-3 py-1 text-sm flex items-center")}
|
||||
onClick={() => onFilterSearchClick({ type: key, value: f.value })}
|
||||
>
|
||||
<Icon className="mr-2" name={option.icon} size="16" />
|
||||
<div className="whitespace-nowrap text-ellipsis overflow-hidden">{f.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Loader>
|
||||
)}
|
||||
|
||||
<div className="" style={{ columns: "100px 2" }}>
|
||||
{filters && Object.keys(filters).map((key) => (
|
||||
<div className="mb-6">
|
||||
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">{key}</div>
|
||||
<div>
|
||||
{filters[key].map((filter: any) => (
|
||||
<div className={cn(stl.optionItem, "flex items-center py-2 cursor-pointer -mx-2 px-2")} onClick={() => onFilterClick(filter)}>
|
||||
<Icon name={filter.icon} size="16"/>
|
||||
<span className="ml-2">{filter.label}</span>
|
||||
</div>
|
||||
))}
|
||||
{ !hasSearchQuery && (
|
||||
<div className="" style={{ columns: "100px 2" }}>
|
||||
{allFilters && Object.keys(allFilters).map((key) => (
|
||||
<div className="mb-6" key={key}>
|
||||
<div className="uppercase font-medium mb-1 color-gray-medium tracking-widest text-sm">{key}</div>
|
||||
<div>
|
||||
{allFilters[key].map((filter: any) => (
|
||||
<div key={filter.label} className={cn(stl.optionItem, "flex items-center py-2 cursor-pointer -mx-2 px-2")} onClick={() => onFilterClick(filter)}>
|
||||
<Icon name={filter.icon} size="16"/>
|
||||
<span className="ml-2">{filter.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -89,4 +103,5 @@ export default connect(state => ({
|
|||
filters: state.getIn([ 'filters', 'filterList' ]),
|
||||
filterSearchList: state.getIn([ 'search', 'filterSearchList' ]),
|
||||
metaOptions: state.getIn([ 'customFields', 'list' ]),
|
||||
fetchingFilterSearchList: state.getIn([ 'search', 'fetchFilterSearch', 'loading' ]),
|
||||
}))(FilterModal);
|
||||
|
|
@ -20,7 +20,11 @@ function FilterSelection(props: Props) {
|
|||
setShowModal(false)
|
||||
}, 200)}
|
||||
>
|
||||
{ children ? React.cloneElement(children, { onClick: () => setShowModal(true)}) : (
|
||||
{ children ? React.cloneElement(children, { onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowModal(true);
|
||||
}}) : (
|
||||
<div
|
||||
className="rounded py-1 px-3 flex items-center cursor-pointer bg-gray-lightest text-ellipsis hover:bg-gray-light-shade"
|
||||
style={{ width: '140px', height: '26px', border: 'solid thin #e9e9e9' }}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,9 @@ function FilterValue(props: Props) {
|
|||
renderValueFiled(filter.value, 0)
|
||||
) : (
|
||||
filter.value && filter.value.map((value, valueIndex) => (
|
||||
renderValueFiled(value, valueIndex)
|
||||
<div key={valueIndex}>
|
||||
{renderValueFiled(value, valueIndex)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const MainSearchBar = (props: Props) => {
|
|||
<span className="font-medium">Clear</span>
|
||||
</Button>
|
||||
}
|
||||
content={'Clear all filters and search'}
|
||||
content={'Clear Steps'}
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top right"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function SaveFunnelButton() {
|
|||
<div>
|
||||
<IconButton
|
||||
className="mr-2"
|
||||
onClick={() => setshowModal(true)} primaryText label="SAVE FUNNEL" icon="zoom-in"
|
||||
onClick={() => setshowModal(true)} primaryText label="SAVE FUNNEL" icon="funnel"
|
||||
/>
|
||||
|
||||
<FunnelSaveModal
|
||||
|
|
|
|||
|
|
@ -74,8 +74,9 @@ function SaveSearchModal(props: Props) {
|
|||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
{ savedSearch && <div className="mt-2">Changes in filters will be updated.</div> }
|
||||
</Modal.Content>
|
||||
<Modal.Actions className="flex items-center">
|
||||
<Modal.Actions className="flex items-center px-6">
|
||||
<div className="mr-auto">
|
||||
<Button
|
||||
primary
|
||||
|
|
@ -86,7 +87,9 @@ function SaveSearchModal(props: Props) {
|
|||
</Button>
|
||||
<Button className={ stl.cancelButton } marginRight onClick={ closeHandler }>{ 'Cancel' }</Button>
|
||||
</div>
|
||||
{ savedSearch && <Button className={ stl.cancelButton } marginRight onClick={ onDelete }>{ 'Delete' }</Button> }
|
||||
{ savedSearch && <Button className={ stl.cancelButton } marginRight onClick={ onDelete }>
|
||||
<Icon name="trash" size="18" />
|
||||
</Button> }
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function Row ({ name, onClick, onClickEdit, onDelete }) {
|
|||
>
|
||||
<div className="px-3 py-2">{name}</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<div className="cursor-pointer px-2 hover:bg-active-blue" onClick={onClickEdit}><Icon name="pencil" size="14" /></div>
|
||||
{/* <div className="cursor-pointer px-2 hover:bg-active-blue" onClick={onClickEdit}><Icon name="pencil" size="14" /></div> */}
|
||||
{/* <div className="cursor-pointer px-2 hover:bg-active-blue" onClick={onDelete}><Icon name="trash" size="14" /></div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -53,7 +53,7 @@ function SavedSearchDropdown(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<div className={cn(stl.wrapper, 'shadow')}>
|
||||
{props.list.map(item => (
|
||||
<Row
|
||||
key={item.searchId}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ import { debounce } from 'App/utils';
|
|||
import { edit as editFilter } from 'Duck/search';
|
||||
import {
|
||||
addEvent, applyFilter, moveEvent, clearEvents,
|
||||
addCustomFilter, addAttribute, setSearchQuery, setActiveFlow, setFilterOption
|
||||
addCustomFilter, addAttribute, setActiveFlow, setFilterOption
|
||||
} from 'Duck/filters';
|
||||
|
||||
interface Props {
|
||||
setSearchQuery: (query: string) => void;
|
||||
// setSearchQuery: (query: string) => void;
|
||||
fetchFilterSearch: (query: any) => void;
|
||||
searchQuery: string;
|
||||
// searchQuery: string;
|
||||
appliedFilter: any;
|
||||
editFilter: typeof editFilter;
|
||||
}
|
||||
|
|
@ -23,9 +23,10 @@ function SessionSearchField(props: Props) {
|
|||
const { appliedFilter } = props;
|
||||
const debounceFetchFilterSearch = debounce(props.fetchFilterSearch, 1000)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
const onSearchChange = (e, { value }) => {
|
||||
// props.setSearchQuery(value)
|
||||
setSearchQuery(value)
|
||||
debounceFetchFilterSearch({ q: value });
|
||||
}
|
||||
|
||||
|
|
@ -58,17 +59,11 @@ function SessionSearchField(props: Props) {
|
|||
autocomplete="off"
|
||||
/>
|
||||
|
||||
{/* <FilterModal
|
||||
close={ () => setShowModal(false) }
|
||||
displayed={ showModal }
|
||||
// displayed={ true }
|
||||
// loading={ loading }
|
||||
// searchedEvents={ searchedEvents }
|
||||
searchQuery={ props.searchQuery }
|
||||
/> */}
|
||||
{ showModal && (
|
||||
<div className="absolute left-0 top-20 border shadow rounded bg-white z-50">
|
||||
<FilterModal
|
||||
searchQuery={searchQuery}
|
||||
isMainSearch={true}
|
||||
onFilterClick={onAddFilter}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -79,8 +74,7 @@ function SessionSearchField(props: Props) {
|
|||
|
||||
export default connect(state => ({
|
||||
events: state.getIn([ 'filters', 'appliedFilter', 'events' ]),
|
||||
// appliedFilter: state.getIn([ 'filters', 'appliedFilter' ]),
|
||||
searchQuery: state.getIn([ 'filters', 'searchQuery' ]),
|
||||
// searchQuery: state.getIn([ 'filters', 'searchQuery' ]),
|
||||
appliedFilterKeys: state.getIn([ 'filters', 'appliedFilter', 'filters' ])
|
||||
.map(({type}) => type).toJS(),
|
||||
searchedEvents: state.getIn([ 'events', 'list' ]),
|
||||
|
|
@ -88,4 +82,4 @@ export default connect(state => ({
|
|||
strict: state.getIn([ 'filters', 'appliedFilter', 'strict' ]),
|
||||
blink: state.getIn([ 'funnels', 'blink' ]),
|
||||
appliedFilter: state.getIn(['search', 'instance']),
|
||||
}), { setSearchQuery, fetchFilterSearch, editFilter })(SessionSearchField);
|
||||
}), { fetchFilterSearch, editFilter })(SessionSearchField);
|
||||
|
|
@ -72,7 +72,6 @@ function reducer(state = initialState, action = {}) {
|
|||
acc[key].push({ projectId, value });
|
||||
return acc;
|
||||
}, {});
|
||||
console.log('groupedList', groupedList);
|
||||
return state.set('filterSearchList', groupedList);
|
||||
case APPLY_SAVED_SEARCH:
|
||||
return state.set('savedSearch', action.filter);
|
||||
|
|
@ -85,6 +84,7 @@ export default mergeReducers(
|
|||
createRequestReducer({
|
||||
[ ROOT_KEY ]: FETCH_LIST,
|
||||
fetch: FETCH,
|
||||
fetchFilterSearch: FETCH_FILTER_SEARCH
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
3
frontend/app/svg/icons/funnel.svg
Normal file
3
frontend/app/svg/icons/funnel.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-funnel" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 361 B |
|
|
@ -47,6 +47,7 @@ export default Record({
|
|||
return filter;
|
||||
});
|
||||
delete series._key
|
||||
delete series.key
|
||||
return series;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Record from 'Types/Record';
|
||||
import { FilterType, FilterKey, FilterCategory } from './filterType'
|
||||
import filterOptions, { countries, platformOptions } from 'App/constants';
|
||||
import { capitalize } from 'App/utils';
|
||||
|
||||
const countryOptions = Object.keys(countries).map(i => ({ text: countries[i], value: i }));
|
||||
|
||||
|
|
@ -55,6 +56,12 @@ export const filtersMap = {
|
|||
[FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS },
|
||||
}
|
||||
|
||||
|
||||
export const getMetaDataFilter = (key) => {
|
||||
const METADATA_FILTER = { key: key, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: capitalize(key), operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata' }
|
||||
return METADATA_FILTER;
|
||||
}
|
||||
|
||||
export default Record({
|
||||
timestamp: 0,
|
||||
key: '',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue