fix(ui) - search rename and save fixes
This commit is contained in:
parent
f0ea5fd63d
commit
a99ba301c6
4 changed files with 386 additions and 380 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { editSavedSearch as edit, save, remove } from 'Duck/search';
|
||||
import { Button, Modal, Form, Icon, Checkbox, Input } from 'UI';
|
||||
|
|
@ -8,128 +8,119 @@ import cn from 'classnames';
|
|||
import { toast } from 'react-toastify';
|
||||
|
||||
interface Props {
|
||||
filter: any;
|
||||
loading: boolean;
|
||||
edit: (filter: any) => void;
|
||||
save: (searchId) => Promise<void>;
|
||||
show: boolean;
|
||||
closeHandler: () => void;
|
||||
savedSearch: any;
|
||||
remove: (filterId: number) => Promise<void>;
|
||||
userId: number;
|
||||
filter: any;
|
||||
loading: boolean;
|
||||
edit: (filter: any) => void;
|
||||
save: (searchId: any, rename: boolean) => Promise<void>;
|
||||
show: boolean;
|
||||
closeHandler: () => void;
|
||||
savedSearch: any;
|
||||
remove: (filterId: number) => Promise<void>;
|
||||
userId: number;
|
||||
rename: boolean;
|
||||
}
|
||||
function SaveSearchModal(props: Props) {
|
||||
const { savedSearch, filter, loading, show, closeHandler } = props;
|
||||
const [name, setName] = useState(savedSearch ? savedSearch.name : '');
|
||||
const { savedSearch, loading, show, closeHandler, rename = false } = props;
|
||||
|
||||
const onNameChange = ({ target: { value } }) => {
|
||||
props.edit({ name: value });
|
||||
// setName(value);
|
||||
};
|
||||
const onNameChange = ({ target: { value } }: any) => {
|
||||
props.edit({ name: value });
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
const { filter, closeHandler } = props;
|
||||
// if (name.trim() === '') return;
|
||||
props.save(savedSearch.exists() ? savedSearch.searchId : null)
|
||||
.then(() => {
|
||||
// this.props.fetchFunnelsList();
|
||||
toast.success(`${savedSearch.exists() ? 'Updated' : 'Saved'} Successfully`);
|
||||
closeHandler();
|
||||
})
|
||||
.catch(e => {
|
||||
toast.error('Something went wrong, please try again');
|
||||
});
|
||||
}
|
||||
const onSave = () => {
|
||||
const { closeHandler } = props;
|
||||
|
||||
const onDelete = async () => {
|
||||
if (await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete this Saved search?`,
|
||||
})) {
|
||||
props.remove(savedSearch.searchId).then(() => {
|
||||
closeHandler();
|
||||
});
|
||||
}
|
||||
}
|
||||
props
|
||||
.save(savedSearch.exists() ? savedSearch.searchId : null, rename)
|
||||
.then(() => {
|
||||
toast.success(`${savedSearch.exists() ? 'Updated' : 'Saved'} Successfully`);
|
||||
closeHandler();
|
||||
})
|
||||
.catch((e) => {
|
||||
toast.error('Something went wrong, please try again');
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeOption = ({ target: { checked, name }}: any) => props.edit({ [ name ]: checked })
|
||||
const onDelete = async () => {
|
||||
if (
|
||||
await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: `Are you sure you want to permanently delete this Saved search?`,
|
||||
})
|
||||
) {
|
||||
props.remove(savedSearch.searchId).then(() => {
|
||||
closeHandler();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeOption = ({ target: { checked, name } }: any) => props.edit({ [name]: checked });
|
||||
|
||||
return (
|
||||
<Modal size="small" open={ show } onClose={closeHandler}>
|
||||
<Modal.Header className={ stl.modalHeader }>
|
||||
<div>{ 'Save Search' }</div>
|
||||
<Icon
|
||||
role="button"
|
||||
tabIndex="-1"
|
||||
color="gray-dark"
|
||||
size="18"
|
||||
name="close"
|
||||
onClick={ closeHandler }
|
||||
/>
|
||||
</Modal.Header>
|
||||
return (
|
||||
<Modal size="small" open={show} onClose={closeHandler}>
|
||||
<Modal.Header className={stl.modalHeader}>
|
||||
<div>{'Save Search'}</div>
|
||||
<Icon role="button" tabIndex="-1" color="gray-dark" size="18" name="close" onClick={closeHandler} />
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Content>
|
||||
<Form onSubmit={onSave}>
|
||||
<Form.Field>
|
||||
<label>{'Title:'}</label>
|
||||
<Input
|
||||
autoFocus={ true }
|
||||
// className={ stl.name }
|
||||
name="name"
|
||||
value={ savedSearch.name }
|
||||
onChange={ onNameChange }
|
||||
placeholder="Title"
|
||||
/>
|
||||
</Form.Field>
|
||||
<Modal.Content>
|
||||
<Form onSubmit={onSave}>
|
||||
<Form.Field>
|
||||
<label>{'Title:'}</label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
// className={ stl.name }
|
||||
name="name"
|
||||
value={savedSearch.name}
|
||||
onChange={onNameChange}
|
||||
placeholder="Title"
|
||||
/>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Field>
|
||||
<div className={cn("flex items-center", { 'disabled': savedSearch.exists() && savedSearch.userId !== props.userId })}>
|
||||
<Checkbox
|
||||
name="isPublic"
|
||||
className="font-medium mr-3"
|
||||
type="checkbox"
|
||||
checked={ savedSearch.isPublic }
|
||||
onClick={ onChangeOption }
|
||||
/>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={ () => props.edit({ 'isPublic' : !savedSearch.isPublic }) }
|
||||
>
|
||||
<Icon name="user-friends" size="16" />
|
||||
<span className="ml-2"> Team Visible</span>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
{ savedSearch.exists() && <div className="mt-4">Changes in filters will be updated.</div> }
|
||||
</Modal.Content>
|
||||
<Modal.Footer className="flex items-center px-6">
|
||||
<div className="mr-auto flex items-center">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ onSave }
|
||||
loading={ loading }
|
||||
disabled={!savedSearch.validate()}
|
||||
className="mr-2"
|
||||
>
|
||||
{ savedSearch.exists() ? 'Update' : 'Create' }
|
||||
</Button>
|
||||
<Button onClick={ closeHandler }>{ 'Cancel' }</Button>
|
||||
</div>
|
||||
{ savedSearch && <Button variant="text" onClick={ onDelete }>
|
||||
<Icon name="trash" size="18" />
|
||||
</Button> }
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
<Form.Field>
|
||||
<div className={cn('flex items-center', { disabled: savedSearch.exists() && savedSearch.userId !== props.userId })}>
|
||||
<Checkbox
|
||||
name="isPublic"
|
||||
className="font-medium mr-3"
|
||||
type="checkbox"
|
||||
checked={savedSearch.isPublic}
|
||||
onClick={onChangeOption}
|
||||
/>
|
||||
<div
|
||||
className="flex items-center cursor-pointer select-none"
|
||||
onClick={() => props.edit({ isPublic: !savedSearch.isPublic })}
|
||||
>
|
||||
<Icon name="user-friends" size="16" />
|
||||
<span className="ml-2"> Team Visible</span>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
{/* {savedSearch.exists() && <div className="mt-4">Changes in filters will be updated.</div>} */}
|
||||
</Modal.Content>
|
||||
<Modal.Footer className="flex items-center px-6">
|
||||
<div className="mr-auto flex items-center">
|
||||
<Button variant="primary" onClick={onSave} loading={loading} disabled={!savedSearch.validate()} className="mr-2">
|
||||
{savedSearch.exists() ? 'Update' : 'Create'}
|
||||
</Button>
|
||||
<Button onClick={closeHandler}>{'Cancel'}</Button>
|
||||
</div>
|
||||
{savedSearch && (
|
||||
<Button variant="text" onClick={onDelete}>
|
||||
<Icon name="trash" size="18" />
|
||||
</Button>
|
||||
)}
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
userId: state.getIn([ 'user', 'account', 'id' ]),
|
||||
savedSearch: state.getIn([ 'search', 'savedSearch' ]),
|
||||
filter: state.getIn(['search', 'instance']),
|
||||
loading: state.getIn([ 'search', 'saveRequest', 'loading' ]) ||
|
||||
state.getIn([ 'search', 'updateRequest', 'loading' ]),
|
||||
}), { edit, save, remove })(SaveSearchModal);
|
||||
export default connect(
|
||||
(state: any) => ({
|
||||
userId: state.getIn(['user', 'account', 'id']),
|
||||
savedSearch: state.getIn(['search', 'savedSearch']),
|
||||
filter: state.getIn(['search', 'instance']),
|
||||
loading: state.getIn(['search', 'saveRequest', 'loading']) || state.getIn(['search', 'updateRequest', 'loading']),
|
||||
}),
|
||||
{ edit, save, remove }
|
||||
)(SaveSearchModal);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import React, { MouseEvent, useState } from 'react'
|
||||
import React, { MouseEvent, useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Icon, Input } from 'UI';
|
||||
import { List } from 'immutable';
|
||||
import { confirm, Popup } from 'UI';
|
||||
import { applySavedSearch, remove, editSavedSearch } from 'Duck/search'
|
||||
import { applySavedSearch, remove, editSavedSearch } from 'Duck/search';
|
||||
import { connect } from 'react-redux';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import { SavedSearch } from 'Types/ts/search'
|
||||
import SaveSearchModal from 'Shared/SaveSearchModal'
|
||||
import stl from './savedSearchModal.module.css'
|
||||
|
||||
import { SavedSearch } from 'Types/ts/search';
|
||||
import SaveSearchModal from 'Shared/SaveSearchModal';
|
||||
import stl from './savedSearchModal.module.css';
|
||||
|
||||
interface ITooltipIcon {
|
||||
title: string;
|
||||
|
|
@ -18,15 +17,12 @@ interface ITooltipIcon {
|
|||
}
|
||||
function TooltipIcon(props: ITooltipIcon) {
|
||||
return (
|
||||
<div onClick={(e) => props.onClick(e)} >
|
||||
<Popup
|
||||
content={props.title}
|
||||
hideOnClick={true}
|
||||
>
|
||||
<div onClick={(e) => props.onClick(e)}>
|
||||
<Popup content={props.title} hideOnClick={true}>
|
||||
<Icon size="16" name={props.name} color="main" />
|
||||
</Popup>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
@ -37,51 +33,55 @@ interface Props {
|
|||
}
|
||||
function SavedSearchModal(props: Props) {
|
||||
const { hideModal } = useModal();
|
||||
const [showModal, setshowModal] = useState(false)
|
||||
const [filterQuery, setFilterQuery] = useState('')
|
||||
const [showModal, setshowModal] = useState(false);
|
||||
const [filterQuery, setFilterQuery] = useState('');
|
||||
|
||||
const onClick = (item: SavedSearch, e) => {
|
||||
e.stopPropagation();
|
||||
props.applySavedSearch(item);
|
||||
hideModal();
|
||||
}
|
||||
};
|
||||
const onDelete = async (item: SavedSearch, e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
const confirmation = await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, delete',
|
||||
confirmation: 'Are you sure you want to permanently delete this search?'
|
||||
})
|
||||
confirmation: 'Are you sure you want to permanently delete this search?',
|
||||
});
|
||||
if (confirmation) {
|
||||
props.remove(item.searchId)
|
||||
props.remove(item.searchId);
|
||||
}
|
||||
}
|
||||
};
|
||||
const onEdit = (item: SavedSearch, e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
props.editSavedSearch(item);
|
||||
setTimeout(() => setshowModal(true), 0);
|
||||
}
|
||||
};
|
||||
|
||||
const shownItems = props.list.filter(item => item.name.includes(filterQuery))
|
||||
const shownItems = props.list.filter((item) => item.name.includes(filterQuery));
|
||||
|
||||
return (
|
||||
<div className="bg-white box-shadow h-screen" style={{ width: '450px' }}>
|
||||
<div className="p-6">
|
||||
<h1 className="text-2xl">Saved Search <span className="color-gray-medium">{props.list.size}</span></h1>
|
||||
<h1 className="text-2xl">
|
||||
Saved Search <span className="color-gray-medium">{props.list.size}</span>
|
||||
</h1>
|
||||
</div>
|
||||
{props.list.size > 1 && (
|
||||
<div className="mb-6 w-full px-4">
|
||||
<Input
|
||||
// className="w-full"
|
||||
// iconPosition="left"
|
||||
icon="search"
|
||||
onChange={({ target: { value }}: any) => setFilterQuery(value)}
|
||||
onChange={({ target: { value } }: any) => setFilterQuery(value)}
|
||||
placeholder="Filter by name"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{shownItems.map(item => (
|
||||
<div key={item.key} className={cn("p-4 cursor-pointer border-b flex items-center group hover:bg-active-blue", item.isPublic && 'pb-10')} onClick={(e) => onClick(item, e)}>
|
||||
{shownItems.map((item) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className={cn('p-4 cursor-pointer border-b flex items-center group hover:bg-active-blue', item.isPublic && 'pb-10')}
|
||||
onClick={(e) => onClick(item, e)}
|
||||
>
|
||||
<Icon name="search" color="gray-medium" size="16" />
|
||||
<div className="ml-4">
|
||||
<div className="text-lg">{item.name} </div>
|
||||
|
|
@ -102,9 +102,9 @@ function SavedSearchModal(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
))}
|
||||
{ showModal && ( <SaveSearchModal show closeHandler={() => setshowModal(false)} /> )}
|
||||
{showModal && <SaveSearchModal show closeHandler={() => setshowModal(false)} rename={true} />}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({ list: state.getIn([ 'search', 'list' ]) }), { applySavedSearch, remove, editSavedSearch })(SavedSearchModal)
|
||||
export default connect((state: any) => ({ list: state.getIn(['search', 'list']) }), { applySavedSearch, remove, editSavedSearch })(SavedSearchModal);
|
||||
|
|
|
|||
|
|
@ -4,27 +4,38 @@ import { Icon } from 'UI';
|
|||
|
||||
interface Props {
|
||||
wrapperClassName?: string;
|
||||
className: string;
|
||||
className?: string;
|
||||
icon?: string;
|
||||
leadingButton?: React.ReactNode;
|
||||
type?: string;
|
||||
rows?: number;
|
||||
[x:string]: any;
|
||||
[x: string]: any;
|
||||
}
|
||||
function Input(props: Props) {
|
||||
const { className, leadingButton = "", wrapperClassName = "", icon = "", type="text", rows=4, ...rest } = props;
|
||||
const { className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props;
|
||||
return (
|
||||
<div className={cn({ "relative" : icon || leadingButton }, wrapperClassName)}>
|
||||
<div className={cn({ relative: icon || leadingButton }, wrapperClassName)}>
|
||||
{icon && <Icon name={icon} className="absolute top-0 bottom-0 my-auto ml-4" size="14" />}
|
||||
{ type === 'textarea' ? (
|
||||
<textarea rows={rows} style={{ resize: 'none' }} maxLength={500} className={ cn("p-2 border border-gray-light bg-white w-full rounded", className, { 'pl-10' : icon }) } {...rest} />
|
||||
{type === 'textarea' ? (
|
||||
<textarea
|
||||
rows={rows}
|
||||
style={{ resize: 'none' }}
|
||||
maxLength={500}
|
||||
className={cn('p-2 border border-gray-light bg-white w-full rounded', className, { 'pl-10': icon })}
|
||||
{...rest}
|
||||
/>
|
||||
) : (
|
||||
<input type={type} style={{ height: '36px'}} className={ cn("p-2 border border-gray-light bg-white w-full rounded", className, { 'pl-10' : icon }) } {...rest} />
|
||||
<input
|
||||
type={type}
|
||||
style={{ height: '36px' }}
|
||||
className={cn('p-2 border border-gray-light bg-white w-full rounded', className, { 'pl-10': icon })}
|
||||
{...rest}
|
||||
/>
|
||||
)}
|
||||
|
||||
{ leadingButton && <div className="absolute top-0 bottom-0 right-0">{ leadingButton }</div> }
|
||||
|
||||
{leadingButton && <div className="absolute top-0 bottom-0 right-0">{leadingButton}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Input;
|
||||
export default Input;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { List, Map } from 'immutable';
|
||||
import { fetchListType, fetchType, saveType, removeType, editType, createRemove } from './funcTools/crud';
|
||||
import { List, Map } from 'immutable';
|
||||
import { fetchListType, fetchType, saveType, removeType, editType } from './funcTools/crud';
|
||||
import { createRequestReducer, ROOT_KEY } from './funcTools/request';
|
||||
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
import { array, success, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
import Filter from 'Types/filter';
|
||||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import { errors as errorsRoute, isRoute } from 'App/routes';
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { FilterCategory, FilterKey, IssueType } from 'Types/filter/filterType';
|
||||
import { filtersMap, liveFiltersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter';
|
||||
import { DURATION_FILTER } from 'App/constants/storageKeys'
|
||||
import { FilterCategory, FilterKey } from 'Types/filter/filterType';
|
||||
import { filtersMap, liveFiltersMap, generateFilterOptions } from 'Types/filter/newFilter';
|
||||
import { DURATION_FILTER } from 'App/constants/storageKeys';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
||||
const name = "search";
|
||||
const idKey = "searchId";
|
||||
const name = 'search';
|
||||
const idKey = 'searchId';
|
||||
|
||||
const FETCH_LIST = fetchListType(name);
|
||||
const FETCH_FILTER_SEARCH = fetchListType(`${name}/FILTER_SEARCH`);
|
||||
|
|
@ -23,7 +23,6 @@ const SAVE = saveType(name);
|
|||
const EDIT = editType(name);
|
||||
const EDIT_SAVED_SEARCH = editType(`${name}/SAVED_SEARCH`);
|
||||
const REMOVE = removeType(name);
|
||||
const ADD_FILTER = `${name}/ADD_FILTER`;
|
||||
const APPLY_SAVED_SEARCH = `${name}/APPLY_SAVED_SEARCH`;
|
||||
const CLEAR_SEARCH = `${name}/CLEAR_SEARCH`;
|
||||
const UPDATE = `${name}/UPDATE`;
|
||||
|
|
@ -36,145 +35,145 @@ const SET_SCROLL_POSITION = `${name}/SET_SCROLL_POSITION`;
|
|||
const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS';
|
||||
|
||||
function chartWrapper(chart = []) {
|
||||
return chart.map(point => ({ ...point, count: Math.max(point.count, 0) }));
|
||||
return chart.map((point) => ({ ...point, count: Math.max(point.count, 0) }));
|
||||
}
|
||||
|
||||
const savedSearchIdKey = 'searchId'
|
||||
const savedSearchIdKey = 'searchId';
|
||||
const updateItemInList = createListUpdater(savedSearchIdKey);
|
||||
const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSearchIdKey ]) === instance[savedSearchIdKey]
|
||||
? state.mergeIn([ "savedSearch" ], SavedFilter(instance))
|
||||
: state;
|
||||
const updateInstance = (state, instance) =>
|
||||
state.getIn(['savedSearch', savedSearchIdKey]) === instance[savedSearchIdKey] ? state.mergeIn(['savedSearch'], SavedFilter(instance)) : state;
|
||||
|
||||
const initialState = Map({
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateFilterOptions(liveFiltersMap),
|
||||
list: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
savedSearch: new SavedFilter({}),
|
||||
filterSearchList: {},
|
||||
currentPage: 1,
|
||||
activeTab: {name: 'All', type: 'all' },
|
||||
scrollY: 0,
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateFilterOptions(liveFiltersMap),
|
||||
list: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
savedSearch: new SavedFilter({}),
|
||||
filterSearchList: {},
|
||||
currentPage: 1,
|
||||
activeTab: { name: 'All', type: 'all' },
|
||||
scrollY: 0,
|
||||
});
|
||||
|
||||
// Metric - Series - [] - filters
|
||||
function reducer(state = initialState, action = {}) {
|
||||
switch (action.type) {
|
||||
case REFRESH_FILTER_OPTIONS:
|
||||
return state.set('filterList', generateFilterOptions(filtersMap))
|
||||
.set('filterListLive', generateFilterOptions(liveFiltersMap));
|
||||
case EDIT:
|
||||
return state.mergeIn(['instance'], action.instance).set('currentPage', 1);
|
||||
case APPLY:
|
||||
return action.fromUrl
|
||||
? state.set('instance', Filter(action.filter))
|
||||
: state.mergeIn(['instance'], action.filter).set('currentPage', 1);
|
||||
case success(FETCH):
|
||||
return state.set("instance", action.data);
|
||||
case success(FETCH_LIST):
|
||||
const { data } = action;
|
||||
return state.set("list", List(data.map(SavedFilter)).sortBy(i => i.searchId));
|
||||
case success(FETCH_FILTER_SEARCH):
|
||||
const groupedList = action.data.reduce((acc, item) => {
|
||||
const { projectId, type, value } = item;
|
||||
const key = type;
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push({ projectId, value });
|
||||
return acc;
|
||||
}, {});
|
||||
return state.set('filterSearchList', groupedList);
|
||||
case APPLY_SAVED_SEARCH:
|
||||
return state.set('savedSearch', action.filter);
|
||||
case EDIT_SAVED_SEARCH:
|
||||
return state.mergeIn([ 'savedSearch' ], action.instance);
|
||||
case UPDATE_CURRENT_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
case SET_ACTIVE_TAB:
|
||||
return state.set('activeTab', action.tab).set('currentPage', 1);
|
||||
case SET_SCROLL_POSITION:
|
||||
return state.set('scrollY', action.scrollPosition);
|
||||
}
|
||||
return state;
|
||||
switch (action.type) {
|
||||
case REFRESH_FILTER_OPTIONS:
|
||||
return state.set('filterList', generateFilterOptions(filtersMap)).set('filterListLive', generateFilterOptions(liveFiltersMap));
|
||||
case EDIT:
|
||||
return state.mergeIn(['instance'], action.instance).set('currentPage', 1);
|
||||
case APPLY:
|
||||
return action.fromUrl ? state.set('instance', Filter(action.filter)) : state.mergeIn(['instance'], action.filter).set('currentPage', 1);
|
||||
case success(FETCH):
|
||||
return state.set('instance', action.data);
|
||||
case success(FETCH_LIST):
|
||||
const { data } = action;
|
||||
return state.set(
|
||||
'list',
|
||||
List(data.map(SavedFilter)).sortBy((i) => i.searchId)
|
||||
);
|
||||
case success(FETCH_FILTER_SEARCH):
|
||||
const groupedList = action.data.reduce((acc, item) => {
|
||||
const { projectId, type, value } = item;
|
||||
const key = type;
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push({ projectId, value });
|
||||
return acc;
|
||||
}, {});
|
||||
return state.set('filterSearchList', groupedList);
|
||||
case APPLY_SAVED_SEARCH:
|
||||
return state.set('savedSearch', action.filter);
|
||||
case EDIT_SAVED_SEARCH:
|
||||
return state.mergeIn(['savedSearch'], action.instance);
|
||||
case UPDATE_CURRENT_PAGE:
|
||||
return state.set('currentPage', action.page);
|
||||
case SET_ACTIVE_TAB:
|
||||
return state.set('activeTab', action.tab).set('currentPage', 1);
|
||||
case SET_SCROLL_POSITION:
|
||||
return state.set('scrollY', action.scrollPosition);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export default mergeReducers(
|
||||
reducer,
|
||||
createRequestReducer({
|
||||
[ ROOT_KEY ]: FETCH_LIST,
|
||||
fetch: FETCH,
|
||||
fetchFilterSearch: FETCH_FILTER_SEARCH
|
||||
}),
|
||||
reducer,
|
||||
createRequestReducer({
|
||||
[ROOT_KEY]: FETCH_LIST,
|
||||
fetch: FETCH,
|
||||
fetchFilterSearch: FETCH_FILTER_SEARCH,
|
||||
})
|
||||
);
|
||||
|
||||
const checkValues = (key, value) => {
|
||||
if (key === FilterKey.DURATION) {
|
||||
return value[0] === '' || value[0] === null ? [0, value[1]] : value;
|
||||
}
|
||||
return value.filter(i => i !== '' && i !== null);
|
||||
}
|
||||
if (key === FilterKey.DURATION) {
|
||||
return value[0] === '' || value[0] === null ? [0, value[1]] : value;
|
||||
}
|
||||
return value.filter((i) => i !== '' && i !== null);
|
||||
};
|
||||
|
||||
export const checkFilterValue = (value) => {
|
||||
return Array.isArray(value) ? (value.length === 0 ? [""] : value) : [value];
|
||||
}
|
||||
return Array.isArray(value) ? (value.length === 0 ? [''] : value) : [value];
|
||||
};
|
||||
|
||||
export const filterMap = ({category, value, key, operator, sourceOperator, source, custom, isEvent, filters, sort, order }) => ({
|
||||
value: checkValues(key, value),
|
||||
custom,
|
||||
type: category === FilterCategory.METADATA ? FilterKey.METADATA : key,
|
||||
operator,
|
||||
source: category === FilterCategory.METADATA ? key : source,
|
||||
sourceOperator,
|
||||
isEvent,
|
||||
filters: filters ? filters.map(filterMap) : [],
|
||||
export const filterMap = ({ category, value, key, operator, sourceOperator, source, custom, isEvent, filters, sort, order }) => ({
|
||||
value: checkValues(key, value),
|
||||
custom,
|
||||
type: category === FilterCategory.METADATA ? FilterKey.METADATA : key,
|
||||
operator,
|
||||
source: category === FilterCategory.METADATA ? key : source,
|
||||
sourceOperator,
|
||||
isEvent,
|
||||
filters: filters ? filters.map(filterMap) : [],
|
||||
});
|
||||
|
||||
export const reduceThenFetchResource = actionCreator => (...args) => (dispatch, getState) => {
|
||||
dispatch(actionCreator(...args));
|
||||
const filter = getState().getIn([ 'search', 'instance']).toData();
|
||||
|
||||
const activeTab = getState().getIn([ 'search', 'activeTab']);
|
||||
if (activeTab.type !== 'all' && activeTab.type !== 'bookmark') {
|
||||
const tmpFilter = filtersMap[FilterKey.ISSUE];
|
||||
tmpFilter.value = [activeTab.type]
|
||||
filter.filters = filter.filters.concat(tmpFilter)
|
||||
}
|
||||
export const reduceThenFetchResource =
|
||||
(actionCreator) =>
|
||||
(...args) =>
|
||||
(dispatch, getState) => {
|
||||
dispatch(actionCreator(...args));
|
||||
const filter = getState().getIn(['search', 'instance']).toData();
|
||||
|
||||
if (activeTab.type === 'bookmark') {
|
||||
filter.bookmarked = true
|
||||
}
|
||||
const activeTab = getState().getIn(['search', 'activeTab']);
|
||||
if (activeTab.type !== 'all' && activeTab.type !== 'bookmark') {
|
||||
const tmpFilter = filtersMap[FilterKey.ISSUE];
|
||||
tmpFilter.value = [activeTab.type];
|
||||
filter.filters = filter.filters.concat(tmpFilter);
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
filter.limit = 10;
|
||||
filter.page = getState().getIn([ 'search', 'currentPage']);
|
||||
if (activeTab.type === 'bookmark') {
|
||||
filter.bookmarked = true;
|
||||
}
|
||||
|
||||
// duration filter from local storage
|
||||
if (!filter.filters.find(f => f.type === FilterKey.DURATION)) {
|
||||
const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}');
|
||||
let durationValue = parseInt(durationFilter.count)
|
||||
if (durationValue > 0) {
|
||||
const value = [0];
|
||||
durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000;
|
||||
if (durationFilter.operator === '<') {
|
||||
value[0] = durationValue;
|
||||
} else if (durationFilter.operator === '>') {
|
||||
value[1] = durationValue;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.concat({
|
||||
type: FilterKey.DURATION,
|
||||
operator: 'is',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
filter.limit = 10;
|
||||
filter.page = getState().getIn(['search', 'currentPage']);
|
||||
|
||||
return isRoute(ERRORS_ROUTE, window.location.pathname)
|
||||
? dispatch(fetchErrorsList(filter))
|
||||
: dispatch(fetchSessionList(filter));
|
||||
};
|
||||
// duration filter from local storage
|
||||
if (!filter.filters.find((f) => f.type === FilterKey.DURATION)) {
|
||||
const durationFilter = JSON.parse(localStorage.getItem(DURATION_FILTER) || '{"count": 0}');
|
||||
let durationValue = parseInt(durationFilter.count);
|
||||
if (durationValue > 0) {
|
||||
const value = [0];
|
||||
durationValue = durationFilter.countType === 'min' ? durationValue * 60 * 1000 : durationValue * 1000;
|
||||
if (durationFilter.operator === '<') {
|
||||
value[0] = durationValue;
|
||||
} else if (durationFilter.operator === '>') {
|
||||
value[1] = durationValue;
|
||||
}
|
||||
|
||||
filter.filters = filter.filters.concat({
|
||||
type: FilterKey.DURATION,
|
||||
operator: 'is',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return isRoute(ERRORS_ROUTE, window.location.pathname) ? dispatch(fetchErrorsList(filter)) : dispatch(fetchSessionList(filter));
|
||||
};
|
||||
|
||||
export const edit = reduceThenFetchResource((instance) => ({
|
||||
type: EDIT,
|
||||
|
|
@ -182,27 +181,27 @@ export const edit = reduceThenFetchResource((instance) => ({
|
|||
}));
|
||||
|
||||
export const setActiveTab = reduceThenFetchResource((tab) => ({
|
||||
type: SET_ACTIVE_TAB,
|
||||
tab
|
||||
type: SET_ACTIVE_TAB,
|
||||
tab,
|
||||
}));
|
||||
|
||||
export const remove = (id) => (dispatch, getState) => {
|
||||
return dispatch({
|
||||
types: REMOVE.array,
|
||||
call: client => client.delete(`/saved_search/${id}`),
|
||||
id,
|
||||
}).then(() => {
|
||||
dispatch(applySavedSearch(new SavedFilter({})));
|
||||
dispatch(fetchList());
|
||||
});
|
||||
return dispatch({
|
||||
types: REMOVE.array,
|
||||
call: (client) => client.delete(`/saved_search/${id}`),
|
||||
id,
|
||||
}).then(() => {
|
||||
dispatch(applySavedSearch(new SavedFilter({})));
|
||||
dispatch(fetchList());
|
||||
});
|
||||
};
|
||||
|
||||
// export const remove = createRemove(name, (id) => `/saved_search/${id}`);
|
||||
|
||||
export const applyFilter = reduceThenFetchResource((filter, fromUrl=false) => ({
|
||||
type: APPLY,
|
||||
filter,
|
||||
fromUrl,
|
||||
export const applyFilter = reduceThenFetchResource((filter, fromUrl = false) => ({
|
||||
type: APPLY,
|
||||
filter,
|
||||
fromUrl,
|
||||
}));
|
||||
|
||||
export const updateCurrentPage = reduceThenFetchResource((page) => ({
|
||||
|
|
@ -211,126 +210,131 @@ export const updateCurrentPage = reduceThenFetchResource((page) => ({
|
|||
}));
|
||||
|
||||
export const applySavedSearch = (filter) => (dispatch, getState) => {
|
||||
dispatch(edit({ filters: filter ? filter.filter.filters : [] }));
|
||||
return dispatch({
|
||||
type: APPLY_SAVED_SEARCH,
|
||||
filter,
|
||||
})
|
||||
dispatch(edit({ filters: filter ? filter.filter.filters : [] }));
|
||||
return dispatch({
|
||||
type: APPLY_SAVED_SEARCH,
|
||||
filter,
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchSessions = (filter) => (dispatch, getState) => {
|
||||
const _filter = filter ? filter : getState().getIn([ 'search', 'instance']);
|
||||
return dispatch(applyFilter(_filter));
|
||||
const _filter = filter ? filter : getState().getIn(['search', 'instance']);
|
||||
return dispatch(applyFilter(_filter));
|
||||
};
|
||||
|
||||
export const updateSeries = (index, series) => ({
|
||||
type: UPDATE,
|
||||
index,
|
||||
series,
|
||||
type: UPDATE,
|
||||
index,
|
||||
series,
|
||||
});
|
||||
|
||||
export function fetch(id) {
|
||||
return {
|
||||
id,
|
||||
types: array(FETCH),
|
||||
call: c => c.get(`/errors/${id}`),
|
||||
}
|
||||
return {
|
||||
id,
|
||||
types: array(FETCH),
|
||||
call: (c) => c.get(`/errors/${id}`),
|
||||
};
|
||||
}
|
||||
|
||||
export const save = (id) => (dispatch, getState) => {
|
||||
const filter = getState().getIn([ 'search', 'instance']).toData();
|
||||
filter.filters = filter.filters.map(filterMap);
|
||||
const isNew = !id;
|
||||
export const save = (id, rename = false) => (dispatch, getState) => {
|
||||
const filter = getState().getIn(['search', 'instance']).toData();
|
||||
// filter.filters = filter.filters.map(filterMap);
|
||||
const isNew = !id;
|
||||
|
||||
const instance = getState().getIn([ 'search', 'savedSearch']).toData();
|
||||
return dispatch({
|
||||
types: SAVE.array,
|
||||
call: client => client.post(isNew ? '/saved_search' : `/saved_search/${id}`, { ...instance, filter })
|
||||
}).then(() => {
|
||||
dispatch(fetchList()).then(() => {
|
||||
if (isNew) {
|
||||
const lastSavedSearch = getState().getIn([ 'search', 'list']).last();
|
||||
dispatch(applySavedSearch(lastSavedSearch));
|
||||
}
|
||||
const instance = getState().getIn(['search', 'savedSearch']).toData();
|
||||
const newInstance = rename ? instance : { ...instance, filter };
|
||||
newInstance.filter.filters = newInstance.filter.filters.map(filterMap);
|
||||
return dispatch({
|
||||
types: SAVE.array,
|
||||
call: (client) => client.post(isNew ? '/saved_search' : `/saved_search/${id}`, newInstance),
|
||||
}).then(() => {
|
||||
dispatch(fetchList()).then(() => {
|
||||
if (isNew) {
|
||||
const lastSavedSearch = getState().getIn(['search', 'list']).last();
|
||||
dispatch(applySavedSearch(lastSavedSearch));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export function fetchList() {
|
||||
return {
|
||||
types: array(FETCH_LIST),
|
||||
call: client => client.get(`/saved_search`),
|
||||
};
|
||||
return {
|
||||
types: array(FETCH_LIST),
|
||||
call: (client) => client.get(`/saved_search`),
|
||||
};
|
||||
}
|
||||
|
||||
export function setAlertMetricId(id) {
|
||||
return {
|
||||
type: SET_ALERT_METRIC_ID,
|
||||
id,
|
||||
};
|
||||
return {
|
||||
type: SET_ALERT_METRIC_ID,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFilterSearch(params) {
|
||||
return {
|
||||
types: FETCH_FILTER_SEARCH.array,
|
||||
call: client => client.get('/events/search', params),
|
||||
params,
|
||||
};
|
||||
return {
|
||||
types: FETCH_FILTER_SEARCH.array,
|
||||
call: (client) => client.get('/events/search', params),
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
export const clearSearch = () => (dispatch, getState) => {
|
||||
dispatch(applySavedSearch(new SavedFilter({})));
|
||||
dispatch(edit(new Filter({ filters: [] })));
|
||||
return dispatch({
|
||||
type: CLEAR_SEARCH,
|
||||
});
|
||||
}
|
||||
dispatch(applySavedSearch(new SavedFilter({})));
|
||||
dispatch(edit(new Filter({ filters: [] })));
|
||||
return dispatch({
|
||||
type: CLEAR_SEARCH,
|
||||
});
|
||||
};
|
||||
|
||||
export const hasFilterApplied = (filters, filter) => {
|
||||
return !filter.isEvent && filters.some(f => f.key === filter.key);
|
||||
}
|
||||
return !filter.isEvent && filters.some((f) => f.key === filter.key);
|
||||
};
|
||||
|
||||
export const addFilter = (filter) => (dispatch, getState) => {
|
||||
filter.value = checkFilterValue(filter.value);
|
||||
filter.filters = filter.filters ? filter.filters.map(subFilter => ({
|
||||
...subFilter,
|
||||
value: checkFilterValue(subFilter.value),
|
||||
})) : null;
|
||||
const instance = getState().getIn([ 'search', 'instance']);
|
||||
filter.value = checkFilterValue(filter.value);
|
||||
filter.filters = filter.filters
|
||||
? filter.filters.map((subFilter) => ({
|
||||
...subFilter,
|
||||
value: checkFilterValue(subFilter.value),
|
||||
}))
|
||||
: null;
|
||||
const instance = getState().getIn(['search', 'instance']);
|
||||
|
||||
if (hasFilterApplied(instance.filters, filter)) {
|
||||
|
||||
} else {
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
}
|
||||
}
|
||||
if (hasFilterApplied(instance.filters, filter)) {
|
||||
} else {
|
||||
const filters = instance.filters.push(filter);
|
||||
return dispatch(edit(instance.set('filters', filters)));
|
||||
}
|
||||
};
|
||||
|
||||
export const addFilterByKeyAndValue = (key, value, operator = undefined) => (dispatch, getState) => {
|
||||
let defaultFilter = filtersMap[key];
|
||||
defaultFilter.value = value;
|
||||
if (operator) {
|
||||
defaultFilter.operator = operator;
|
||||
}
|
||||
dispatch(addFilter(defaultFilter));
|
||||
}
|
||||
export const addFilterByKeyAndValue =
|
||||
(key, value, operator = undefined) =>
|
||||
(dispatch, getState) => {
|
||||
let defaultFilter = filtersMap[key];
|
||||
defaultFilter.value = value;
|
||||
if (operator) {
|
||||
defaultFilter.operator = operator;
|
||||
}
|
||||
dispatch(addFilter(defaultFilter));
|
||||
};
|
||||
|
||||
export const editSavedSearch = instance => {
|
||||
return {
|
||||
type: EDIT_SAVED_SEARCH,
|
||||
instance,
|
||||
}
|
||||
export const editSavedSearch = (instance) => {
|
||||
return {
|
||||
type: EDIT_SAVED_SEARCH,
|
||||
instance,
|
||||
};
|
||||
};
|
||||
|
||||
export const refreshFilterOptions = () => {
|
||||
return {
|
||||
type: REFRESH_FILTER_OPTIONS
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: REFRESH_FILTER_OPTIONS,
|
||||
};
|
||||
};
|
||||
|
||||
export const setScrollPosition = (scrollPosition) => {
|
||||
return {
|
||||
type: SET_SCROLL_POSITION,
|
||||
scrollPosition,
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: SET_SCROLL_POSITION,
|
||||
scrollPosition,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue