change(ui) - fixes in metric session list and other chanegs
This commit is contained in:
parent
b33d05f1e6
commit
696f23b2ba
9 changed files with 106 additions and 59 deletions
|
|
@ -26,6 +26,7 @@ import TrackerUpdateMessage from 'Shared/TrackerUpdateMessage';
|
|||
import LiveSessionList from './LiveSessionList'
|
||||
import SessionSearch from 'Shared/SessionSearch';
|
||||
import MainSearchBar from 'Shared/MainSearchBar';
|
||||
import LiveSearchBar from 'Shared/LiveSearchBar';
|
||||
import { clearSearch } from 'Duck/search';
|
||||
|
||||
const weakEqual = (val1, val2) => {
|
||||
|
|
@ -168,15 +169,28 @@ export default class BugFinder extends React.PureComponent {
|
|||
<div className={cn("side-menu-margined", stl.searchWrapper) }>
|
||||
<TrackerUpdateMessage />
|
||||
<NoSessionsMessage />
|
||||
<div
|
||||
data-hidden={ activeTab === 'live' || activeTab === 'favorite' }
|
||||
className="mb-5"
|
||||
>
|
||||
<MainSearchBar />
|
||||
<SessionSearch />
|
||||
</div>
|
||||
{ activeTab.type !== 'live' && <SessionList onMenuItemClick={this.setActiveTab} /> }
|
||||
{ activeTab.type === 'live' && <LiveSessionList /> }
|
||||
|
||||
{/* Recorde Sessions */}
|
||||
{ activeTab.type !== 'live' && (
|
||||
<>
|
||||
<div className="mb-5">
|
||||
<MainSearchBar />
|
||||
<SessionSearch />
|
||||
</div>
|
||||
{ activeTab.type !== 'live' && <SessionList onMenuItemClick={this.setActiveTab} /> }
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Live Sessions */}
|
||||
{ activeTab.type === 'live' && (
|
||||
<>
|
||||
<div className="mb-5">
|
||||
<LiveSearchBar />
|
||||
<SessionSearch />
|
||||
</div>
|
||||
{ activeTab.type === 'live' && <LiveSessionList /> }
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<RehydrateSlidePanel
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { SlideModal, NoContent, Dropdown, Icon, TimezoneDropdown } from 'UI';
|
||||
import { SlideModal, NoContent, Dropdown, Icon, TimezoneDropdown, Loader } from 'UI';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
import stl from './SessionListModal.css';
|
||||
import { connect } from 'react-redux';
|
||||
|
|
@ -40,11 +40,26 @@ function SessionListModal(props: Props) {
|
|||
]);
|
||||
}, [list]);
|
||||
|
||||
const writeOption = (e, { name, value }) => setActiveSeries(value);
|
||||
const getListSessionsBySeries = (seriesId) => {
|
||||
const arr: any = []
|
||||
list.forEach(element => {
|
||||
if (seriesId === 'all') {
|
||||
const sessionIds = arr.map(i => i.sessionId);
|
||||
arr.push(...element.sessions.filter(i => !sessionIds.includes(i.sessionId)));
|
||||
} else {
|
||||
if (element.seriesId === seriesId) {
|
||||
arr.push(...element.sessions)
|
||||
}
|
||||
}
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
const filteredSessions = activeSeries === 'all' ? list.reduce((a, b) => a.concat(b.sessions), []) : list.filter(item => item.seriesId === activeSeries).reduce((a, b) => a.concat(b.sessions), []);
|
||||
const startTime = new DateTime(activeWidget.startTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
const endTime = new DateTime(activeWidget.endTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
const writeOption = (e, { name, value }) => setActiveSeries(value);
|
||||
const filteredSessions = getListSessionsBySeries(activeSeries);
|
||||
|
||||
const startTime = DateTime.fromMillis(activeWidget.startTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
const endTime = DateTime.fromMillis(activeWidget.endTimestamp).toFormat('LLL dd, yyyy HH:mm a');
|
||||
return (
|
||||
<SlideModal
|
||||
title={ activeWidget && (
|
||||
|
|
@ -80,13 +95,15 @@ function SessionListModal(props: Props) {
|
|||
{/* <span className="mr-2 color-gray-medium">Series</span> */}
|
||||
</div>
|
||||
</div>
|
||||
<NoContent
|
||||
show={ !loading && (filteredSessions.length === 0 || filteredSessions.size === 0 )}
|
||||
title="No recordings found!"
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
{ filteredSessions.map(session => <SessionItem key={ session.sessionId } session={ session } />) }
|
||||
</NoContent>
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
show={ !loading && (filteredSessions.length === 0 )}
|
||||
title="No recordings found!"
|
||||
icon="exclamation-circle"
|
||||
>
|
||||
{ filteredSessions.map(session => <SessionItem key={ session.sessionId } session={ session } />) }
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -94,7 +111,7 @@ function SessionListModal(props: Props) {
|
|||
}
|
||||
|
||||
export default connect(state => ({
|
||||
loading: state.getIn(['customMetrics', 'sessionListRequest', 'loading']),
|
||||
loading: state.getIn(['customMetrics', 'fetchSessionList', 'loading']),
|
||||
list: state.getIn(['customMetrics', 'sessionList']),
|
||||
// activeWidget: state.getIn(['customMetrics', 'activeWidget']),
|
||||
}), { fetchSessionList, setActiveWidget })(SessionListModal);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
|||
import cn from 'classnames';
|
||||
import stl from './FilterModal.css';
|
||||
import { filtersMap, getMetaDataFilter } from 'Types/filter/newFilter';
|
||||
// import { FilterKey, FilterType } from 'Types/filter/filterType';
|
||||
|
||||
interface Props {
|
||||
filters: any,
|
||||
|
|
@ -25,7 +24,6 @@ function FilterModal(props: Props) {
|
|||
fetchingFilterSearchList,
|
||||
searchQuery = '',
|
||||
} = props;
|
||||
// const hasFilerSearchList = filterSearchList && Object.keys(filterSearchList).length > 0;
|
||||
const hasSearchQuery = searchQuery && searchQuery.length > 0;
|
||||
const showSearchList = isMainSearch && searchQuery.length > 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import SessionSearchField from 'Shared/SessionSearchField';
|
||||
import SavedSearch from 'Shared/SavedSearch';
|
||||
import { Button, Popup } from 'UI';
|
||||
import { clearSearch } from 'Duck/search';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface Props {
|
||||
clearSearch: () => void;
|
||||
appliedFilter: any;
|
||||
}
|
||||
const LiveSearchBar = (props: Props) => {
|
||||
const { appliedFilter } = props;
|
||||
const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0;
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div style={{ width: "80%", marginRight: "10px"}}>
|
||||
<SessionSearchField />
|
||||
</div>
|
||||
<div className="flex items-center" style={{ width: "20%"}}>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button
|
||||
plain
|
||||
disabled={!hasFilters}
|
||||
className="ml-auto"
|
||||
onClick={() => props.clearSearch()}
|
||||
>
|
||||
<span className="font-medium">Clear</span>
|
||||
</Button>
|
||||
}
|
||||
content={'Clear Steps'}
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default connect(state => ({
|
||||
appliedFilter: state.getIn(['search', 'instance']),
|
||||
}), { clearSearch })(LiveSearchBar);
|
||||
1
frontend/app/components/shared/LiveSearchBar/index.ts
Normal file
1
frontend/app/components/shared/LiveSearchBar/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './LiveSearchBar';
|
||||
|
|
@ -1,26 +1,18 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import stl from './SessionSearchField.css';
|
||||
import { Input } from 'UI';
|
||||
import FilterModal from 'Shared/Filters/FilterModal';
|
||||
// import { fetchList as fetchFilterSearch } from 'Duck/events';
|
||||
import { fetchFilterSearch } from 'Duck/search';
|
||||
import { debounce } from 'App/utils';
|
||||
import { edit as editFilter } from 'Duck/search';
|
||||
import {
|
||||
addEvent, applyFilter, moveEvent, clearEvents,
|
||||
addCustomFilter, addAttribute, setActiveFlow, setFilterOption
|
||||
} from 'Duck/filters';
|
||||
import { edit as editFilter, addFilterByKeyAndValue } from 'Duck/search';
|
||||
|
||||
interface Props {
|
||||
// setSearchQuery: (query: string) => void;
|
||||
fetchFilterSearch: (query: any) => void;
|
||||
// searchQuery: string;
|
||||
appliedFilter: any;
|
||||
editFilter: typeof editFilter;
|
||||
addFilterByKeyAndValue: (key: string, value: string) => void;
|
||||
}
|
||||
function SessionSearchField(props: Props) {
|
||||
const { appliedFilter } = props;
|
||||
const debounceFetchFilterSearch = debounce(props.fetchFilterSearch, 1000)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
|
@ -31,12 +23,7 @@ function SessionSearchField(props: Props) {
|
|||
}
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
filter.value = filter.value ? filter.value : [""]
|
||||
const newFilters = appliedFilter.filters.concat(filter);
|
||||
props.editFilter({
|
||||
...appliedFilter.filter,
|
||||
filters: newFilters,
|
||||
});
|
||||
props.addFilterByKeyAndValue(filter.key, filter.value)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -46,10 +33,7 @@ function SessionSearchField(props: Props) {
|
|||
className={stl.searchField}
|
||||
onFocus={ () => setShowModal(true) }
|
||||
onBlur={ () => setTimeout(setShowModal, 200, false) }
|
||||
// ref={ this.inputRef }
|
||||
onChange={ onSearchChange }
|
||||
// onKeyUp={this.onKeyUp}
|
||||
// value={props.searchQuery}
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
placeholder={ 'Search sessions using any captured event (click, input, page, error...)'}
|
||||
|
|
@ -72,14 +56,4 @@ function SessionSearchField(props: Props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
events: state.getIn([ 'filters', 'appliedFilter', 'events' ]),
|
||||
// searchQuery: state.getIn([ 'filters', 'searchQuery' ]),
|
||||
appliedFilterKeys: state.getIn([ 'filters', 'appliedFilter', 'filters' ])
|
||||
.map(({type}) => type).toJS(),
|
||||
searchedEvents: state.getIn([ 'events', 'list' ]),
|
||||
loading: state.getIn([ 'events', 'loading' ]),
|
||||
strict: state.getIn([ 'filters', 'appliedFilter', 'strict' ]),
|
||||
blink: state.getIn([ 'funnels', 'blink' ]),
|
||||
appliedFilter: state.getIn(['search', 'instance']),
|
||||
}), { fetchFilterSearch, editFilter })(SessionSearchField);
|
||||
export default connect(null, { fetchFilterSearch, editFilter, addFilterByKeyAndValue })(SessionSearchField);
|
||||
|
|
@ -20,9 +20,9 @@ class SegmentSelection extends React.Component {
|
|||
>
|
||||
{ list.map(item => (
|
||||
<Popup
|
||||
key={ item.name }
|
||||
trigger={
|
||||
<div
|
||||
key={ item.name }
|
||||
className={ cn(styles.item, { 'opacity-25 cursor-default' : item.disabled }) }
|
||||
data-active={ this.props.value && this.props.value.value === item.value }
|
||||
onClick={ () => !item.disabled && this.setActiveItem(item) }
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const getChartFormatter = period => (data = []) =>
|
|||
|
||||
export const getStartAndEndTimestampsByDensity = (current, start, end, density) => {
|
||||
const diff = end - start;
|
||||
const step = diff / density;
|
||||
const step = Math.floor(diff / density);
|
||||
const currentIndex = Math.floor((current - start) / step);
|
||||
const startTimestamp = parseInt(start + currentIndex * step);
|
||||
const endTimestamp = parseInt(startTimestamp + step);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ export const issues_types = List([
|
|||
{ 'type': 'js_exception', 'visible': true, 'order': 0, 'name': 'Errors', 'icon': 'funnel/exclamation-circle' },
|
||||
{ 'type': 'bad_request', 'visible': true, 'order': 1, 'name': 'Bad Requests', 'icon': 'funnel/file-medical-alt' },
|
||||
{ 'type': 'missing_resource', 'visible': true, 'order': 2, 'name': 'Missing Images', 'icon': 'funnel/image' },
|
||||
{ 'type': 'click_rage', 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/dizzy' },
|
||||
{ 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/emoji-angry' },
|
||||
{ 'type': 'click_rage', 'visible': true, 'order': 3, 'name': 'Click Rage', 'icon': 'funnel/emoji-angry' },
|
||||
{ 'type': 'dead_click', 'visible': true, 'order': 4, 'name': 'Dead Clicks', 'icon': 'funnel/dizzy' },
|
||||
{ 'type': 'memory', 'visible': true, 'order': 5, 'name': 'High Memory', 'icon': 'funnel/sd-card' },
|
||||
{ 'type': 'cpu', 'visible': true, 'order': 6, 'name': 'High CPU', 'icon': 'funnel/cpu' },
|
||||
{ 'type': 'crash', 'visible': true, 'order': 7, 'name': 'Crashes', 'icon': 'funnel/file-earmark-break' },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue