feat(ui) - assist filter auto focus

This commit is contained in:
Shekar Siri 2022-02-10 22:55:03 +01:00
parent a6af2cfb46
commit 8dd7578169
14 changed files with 117 additions and 59 deletions

View file

@ -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"

View file

@ -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>
);
}

View file

@ -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>
</>
);
}

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1 @@
export { default } from './CustomMetricsModal';

View file

@ -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">

View file

@ -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

View file

@ -0,0 +1 @@
export { default } from './SessionCopyLink';

View file

@ -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>

View file

@ -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);
});

View file

@ -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));
}

View file

@ -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:

View file

@ -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 {