feat(ui) - assist filter auto focus
This commit is contained in:
parent
a6af2cfb46
commit
8dd7578169
14 changed files with 117 additions and 59 deletions
|
|
@ -84,7 +84,7 @@ function LiveSessionList(props: Props) {
|
|||
title={"No live sessions."}
|
||||
subtext={
|
||||
<span>
|
||||
See how to <a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">{'enable Assist'}</a> if you haven't yet done so.
|
||||
See how to <a target="_blank" className="link" href="https://docs.openreplay.com/plugins/assist">{'enable Assist'}</a> and ensure you're using tracker-assist v3.5.0 or higher.
|
||||
</span>
|
||||
}
|
||||
image={<img src="/img/live-sessions.png"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import withPermissions from 'HOCs/withPermissions'
|
|||
import { setPeriod, setPlatform, fetchMetadataOptions } from 'Duck/dashboard';
|
||||
import { NoContent, Icon } from 'UI';
|
||||
import { WIDGET_KEYS, WIDGET_LIST } from 'Types/dashboard';
|
||||
import CustomMetrics from 'Shared/CustomMetrics';
|
||||
// import CustomMetrics from 'Shared/CustomMetrics';
|
||||
import CustomMetricsModal from 'Shared/CustomMetrics/CustomMetricsModal';
|
||||
import SessionListModal from 'Shared/CustomMetrics/SessionListModal';
|
||||
|
||||
import {
|
||||
|
|
@ -234,12 +235,12 @@ export default class Dashboard extends React.PureComponent {
|
|||
description={
|
||||
<div className="flex items-center">
|
||||
{comparing && (
|
||||
<div className="mr-4 text-sm flex items-center font-normal">
|
||||
<div className="text-sm flex items-center font-normal">
|
||||
<Icon name="info" size="12" className="mr-2" />
|
||||
Custom Metrics are not supported for comparison.
|
||||
</div>
|
||||
)}
|
||||
<CustomMetrics />
|
||||
{/* <CustomMetrics /> */}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
|
|
@ -297,6 +298,8 @@ export default class Dashboard extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomMetricsModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import stl from './sideMenuSection.css';
|
|||
import { connect } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { withSiteId } from 'App/routes';
|
||||
import CustomMetrics from 'Shared/CustomMetrics';
|
||||
|
||||
function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) {
|
||||
return (
|
||||
|
|
@ -29,6 +30,13 @@ function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) {
|
|||
onClick={() => setShowAlerts(true)}
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,17 @@
|
|||
import React, { useState } from 'react';
|
||||
import { IconButton, SlideModal } from 'UI';
|
||||
import CustomMetricForm from './CustomMetricForm';
|
||||
import React from 'react';
|
||||
import { IconButton } from 'UI';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit, init } from 'Duck/customMetrics';
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
edit: (metric) => void;
|
||||
instance: any;
|
||||
init: (instance?, setDefault?) => void;
|
||||
}
|
||||
function CustomMetrics(props: Props) {
|
||||
const { metric } = props;
|
||||
|
||||
return (
|
||||
<div className="self-start">
|
||||
<IconButton plain outline icon="plus" label="CREATE METRIC" onClick={() => props.init()} />
|
||||
|
||||
<SlideModal
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<span className="mr-3">{ metric && metric.exists() ? 'Update Custom Metric' : 'Create Custom Metric' }</span>
|
||||
</div>
|
||||
}
|
||||
isDisplayed={ !!metric }
|
||||
onClose={ () => props.init(null, true)}
|
||||
content={ (!!metric) && (
|
||||
<div style={{ backgroundColor: '#f6f6f6' }}>
|
||||
<CustomMetricForm metric={metric} onClose={() => props.init(null, true)} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
metric: state.getIn(['customMetrics', 'instance']),
|
||||
alertInstance: state.getIn(['alerts', 'instance']),
|
||||
showModal: state.getIn(['customMetrics', 'showModal']),
|
||||
}), { edit, init })(CustomMetrics);
|
||||
export default connect(null, { edit, init })(CustomMetrics);
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import React from 'react'
|
||||
import { IconButton, SlideModal } from 'UI';
|
||||
import CustomMetricForm from '../CustomMetricForm';
|
||||
import { connect } from 'react-redux'
|
||||
import { init } from 'Duck/customMetrics';
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
init: (instance?, setDefault?) => void;
|
||||
}
|
||||
function CustomMetricsModal(props: Props) {
|
||||
const { metric } = props;
|
||||
return (
|
||||
<>
|
||||
<SlideModal
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<span className="mr-3">{ metric && metric.exists() ? 'Update Custom Metric' : 'Create Custom Metric' }</span>
|
||||
</div>
|
||||
}
|
||||
isDisplayed={ !!metric }
|
||||
onClose={ () => props.init(null, true)}
|
||||
content={ (!!metric) && (
|
||||
<div style={{ backgroundColor: '#f6f6f6' }}>
|
||||
<CustomMetricForm metric={metric} onClose={() => props.init(null, true)} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default connect(state => ({
|
||||
metric: state.getIn(['customMetrics', 'instance']),
|
||||
alertInstance: state.getIn(['alerts', 'instance']),
|
||||
showModal: state.getIn(['customMetrics', 'showModal']),
|
||||
}), { init })(CustomMetricsModal);
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './CustomMetricsModal';
|
||||
|
|
@ -39,7 +39,7 @@ function FilterAutoComplete(props: Props) {
|
|||
value = '',
|
||||
icon = null,
|
||||
} = props;
|
||||
const [showModal, setShowModal] = useState(true)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [options, setOptions] = useState<any>([]);
|
||||
const [query, setQuery] = useState(value);
|
||||
|
|
@ -64,15 +64,23 @@ function FilterAutoComplete(props: Props) {
|
|||
|
||||
const onInputChange = ({ target: { value } }) => {
|
||||
setQuery(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (query === '' || query === ' ') {
|
||||
return
|
||||
if (!showModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
|
||||
debouncedRequestValues(query)
|
||||
}, [query])
|
||||
if (value === '' || value === ' ') {
|
||||
return
|
||||
}
|
||||
debouncedRequestValues(value);
|
||||
}
|
||||
|
||||
// useEffect(() => {
|
||||
// if (query === '' || query === ' ') {
|
||||
// return
|
||||
// }
|
||||
|
||||
// debouncedRequestValues(query)
|
||||
// }, [query])
|
||||
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
|
|
@ -106,7 +114,7 @@ function FilterAutoComplete(props: Props) {
|
|||
name="query"
|
||||
onChange={ onInputChange }
|
||||
onBlur={ onBlur }
|
||||
onFocus={ () => setShowModal(true)}
|
||||
// onFocus={ () => setShowModal(true)}
|
||||
value={ query }
|
||||
autoFocus={ true }
|
||||
type="text"
|
||||
|
|
@ -130,7 +138,7 @@ function FilterAutoComplete(props: Props) {
|
|||
|
||||
{/* <textarea style={hiddenStyle} ref={(ref) => this.hiddenInput = ref }></textarea> */}
|
||||
|
||||
{ showModal && (options.length > 0 || loading) &&
|
||||
{ showModal &&
|
||||
<div className={ stl.menu }>
|
||||
{ headerText && headerText }
|
||||
<Loader loading={loading} size="small">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react'
|
||||
import { IconButton } from 'UI'
|
||||
|
||||
function SessionCopyLink() {
|
||||
return (
|
||||
<div className="flex justify-between items-center w-full border-t -mx-4 px-4">
|
||||
<IconButton label="Copy Link" icon="link-45deg" />
|
||||
<div>Copied to Clipboard</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SessionCopyLink
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SessionCopyLink';
|
||||
|
|
@ -5,6 +5,7 @@ import { Popup, Dropdown, Icon, IconButton } from 'UI';
|
|||
import { pause } from 'Player';
|
||||
import styles from './sharePopup.css';
|
||||
import IntegrateSlackButton from '../IntegrateSlackButton/IntegrateSlackButton';
|
||||
import SessionCopyLink from './SessionCopyLink';
|
||||
|
||||
@connect(state => ({
|
||||
channels: state.getIn([ 'slack', 'list' ]),
|
||||
|
|
@ -62,9 +63,14 @@ export default class SharePopup extends React.PureComponent {
|
|||
<div className={ styles.title }>{ 'Comment' }</div>
|
||||
</div>
|
||||
{ options.length === 0 ?
|
||||
<div className={ styles.body }>
|
||||
<IntegrateSlackButton />
|
||||
</div>
|
||||
<>
|
||||
<div className={ styles.body }>
|
||||
<IntegrateSlackButton />
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<SessionCopyLink />
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<div>
|
||||
<div className={ styles.body }>
|
||||
|
|
@ -97,7 +103,10 @@ export default class SharePopup extends React.PureComponent {
|
|||
{ loading ? 'Sharing...' : 'Share' }
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<SessionCopyLink />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ const initialState = Map({
|
|||
const reducer = (state = initialState, action = {}) => {
|
||||
switch(action.type) {
|
||||
case FETCH_SUCCESS:
|
||||
console.log('FETCH_SUCCESS', action.data);
|
||||
action.data.forEach(item => {
|
||||
addElementToFiltersMap(FilterCategory.METADATA, item.key);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { mergeReducers } from './funcTools/tools';
|
|||
import Filter from 'Types/filter';
|
||||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { filtersMap } from 'Types/filter/newFilter';
|
||||
import { liveFiltersMap } from 'Types/filter/newFilter';
|
||||
import { filterMap, checkFilterValue, hasFilterApplied } from './search';
|
||||
|
||||
const name = "liveSearch";
|
||||
|
|
@ -86,7 +86,7 @@ export const addFilter = (filter) => (dispatch, getState) => {
|
|||
}
|
||||
|
||||
export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => {
|
||||
let defaultFilter = filtersMap[key];
|
||||
let defaultFilter = liveFiltersMap[key];
|
||||
defaultFilter.value = value;
|
||||
dispatch(addFilter(defaultFilter));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { errors as errorsRoute, isRoute } from "App/routes";
|
|||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { FilterCategory, FilterKey } from '../types/filter/filterType';
|
||||
import { filtersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter';
|
||||
import { filtersMap, liveFiltersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter';
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSe
|
|||
|
||||
const initialState = Map({
|
||||
filterList: generateFilterOptions(filtersMap),
|
||||
filterListLive: generateLiveFilterOptions(filtersMap),
|
||||
filterListLive: generateLiveFilterOptions(liveFiltersMap),
|
||||
list: List(),
|
||||
alertMetricId: null,
|
||||
instance: new Filter({ filters: [] }),
|
||||
|
|
@ -56,7 +56,7 @@ function reducer(state = initialState, action = {}) {
|
|||
switch (action.type) {
|
||||
case REFRESH_FILTER_OPTIONS:
|
||||
return state.set('filterList', generateFilterOptions(filtersMap))
|
||||
.set('filterListLive', generateLiveFilterOptions(filtersMap));
|
||||
.set('filterListLive', generateLiveFilterOptions(liveFiltersMap));
|
||||
case EDIT:
|
||||
return state.mergeIn(['instance'], action.instance);
|
||||
case APPLY:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const filtersMap = {
|
|||
[FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' },
|
||||
[FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions },
|
||||
// [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' },
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid', isLive: true },
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
|
||||
// PERFORMANCE
|
||||
|
|
@ -56,6 +56,10 @@ 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 liveFiltersMap = {
|
||||
[FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.STRING, category: FilterCategory.USER, label: 'User Id', operator: 'contains', operatorOptions: [{ key: 'contains', text: 'contains', value: 'contains' }], icon: 'filters/userid', isLive: true },
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new filter to the filter list
|
||||
* @param {*} category
|
||||
|
|
@ -138,17 +142,16 @@ export const generateFilterOptions = (filtersMap) => {
|
|||
return _options;
|
||||
}
|
||||
|
||||
export const generateLiveFilterOptions = (filtersMap) => {
|
||||
export const generateLiveFilterOptions = (map) => {
|
||||
const _options = {};
|
||||
Object.keys(filtersMap).filter(i => filtersMap[i].isLive).forEach(key => {
|
||||
const filter = filtersMap[key];
|
||||
|
||||
Object.keys(map).filter(i => map[i].isLive).forEach(key => {
|
||||
const filter = map[key];
|
||||
filter.operator = 'contains';
|
||||
filter.type = FilterType.STRING;
|
||||
// filter.type = FilterType.STRING;
|
||||
// filter.type = FilterType.AUTOCOMPLETE_LOCAL;
|
||||
// filter.options = countryOptions;
|
||||
filter.operatorOptions = [
|
||||
{ key: 'contains', text: 'contains', value: 'contains' },
|
||||
]
|
||||
// filter.operatorOptions = [{ key: 'contains', text: 'contains', value: 'contains' }]
|
||||
if (_options.hasOwnProperty(filter.category)) {
|
||||
_options[filter.category].push(filter);
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue