feat(ui) - custom metric widgets active inactive
This commit is contained in:
parent
fece5f1bca
commit
3e7634c043
8 changed files with 87 additions and 111 deletions
|
|
@ -18,11 +18,11 @@ export default class AddWidgets extends React.PureComponent {
|
|||
const { appearance } = this.props;
|
||||
const newAppearance = appearance.setIn([ 'dashboard', widgetKey ], true);
|
||||
this.props.switchOpen(false);
|
||||
this.props.updateAppearance(newAppearance)
|
||||
this.props.updateAppearance(newAppearance)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { appearance, disabled } = this.props;
|
||||
const { appearance } = this.props;
|
||||
const avaliableWidgets = WIDGET_LIST.filter(({ key, type }) => !appearance.dashboard[ key ] && type === this.props.type );
|
||||
|
||||
return (
|
||||
|
|
@ -44,46 +44,6 @@ export default class AddWidgets extends React.PureComponent {
|
|||
</div>
|
||||
}
|
||||
</OutsideClickDetectingDiv>
|
||||
|
||||
<SlideModal
|
||||
title="Add Widget"
|
||||
size="middle"
|
||||
isDisplayed={ false }
|
||||
content={ this.props.open &&
|
||||
<NoContent
|
||||
title="No Widgets Left"
|
||||
size="small"
|
||||
show={ avaliableWidgets.length === 0}
|
||||
>
|
||||
{ avaliableWidgets.map(({ key, name, description, thumb }) => (
|
||||
<div className={ cn(stl.widgetCard) } key={ key } >
|
||||
<div className="flex justify-between items-center flex-1">
|
||||
<div className="mr-10">
|
||||
<h4>{ name }</h4>
|
||||
</div>
|
||||
<IconButton
|
||||
className="flex-shrink-0"
|
||||
onClick={ this.makeAddHandler(key) }
|
||||
circle
|
||||
outline
|
||||
icon="plus"
|
||||
style={{ width: '24px', height: '24px'}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</NoContent>
|
||||
}
|
||||
onClose={ this.props.switchOpen }
|
||||
/>
|
||||
<IconButton
|
||||
circle
|
||||
size="small"
|
||||
icon="plus"
|
||||
outline
|
||||
onClick={ this.props.switchOpen }
|
||||
disabled={ disabled || avaliableWidgets.length === 0 } //TODO disabled after Custom fields filtering
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import withPageTitle from 'HOCs/withPageTitle';
|
|||
import withPermissions from 'HOCs/withPermissions'
|
||||
import { setPeriod, setPlatform, fetchMetadataOptions } from 'Duck/dashboard';
|
||||
import { NoContent, Icon } from 'UI';
|
||||
import { WIDGET_KEYS } from 'Types/dashboard';
|
||||
import { WIDGET_KEYS, WIDGET_LIST } from 'Types/dashboard';
|
||||
import CustomMetrics from 'Shared/CustomMetrics';
|
||||
import SessionListModal from 'Shared/CustomMetrics/SessionListModal';
|
||||
|
||||
|
|
@ -124,6 +124,7 @@ function isInViewport(el) {
|
|||
platform: state.getIn([ 'dashboard', 'platform' ]),
|
||||
dashboardAppearance: state.getIn([ 'user', 'account', 'appearance', 'dashboard' ]),
|
||||
activeWidget: state.getIn(['customMetrics', 'activeWidget']),
|
||||
appearance: state.getIn([ 'user', 'account', 'appearance' ]),
|
||||
}), { setPeriod, setPlatform, fetchMetadataOptions })
|
||||
@withPageTitle('Metrics - OpenReplay')
|
||||
@withRouter
|
||||
|
|
@ -144,6 +145,10 @@ export default class Dashboard extends React.PureComponent {
|
|||
pageSection: 'metrics',
|
||||
};
|
||||
|
||||
getWidgetsByKey = (widgetType) => {
|
||||
return WIDGET_LIST.filter(({ key, type }) => !this.props.appearance.dashboard[ key ] && type === widgetType);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { history, location } = this.props;
|
||||
// TODO check the hash navigato it
|
||||
|
|
@ -209,7 +214,13 @@ export default class Dashboard extends React.PureComponent {
|
|||
icon
|
||||
empty
|
||||
>
|
||||
<WidgetSection title="Overview" type="overview" className="mb-4" description="(Average Values)">
|
||||
<WidgetSection
|
||||
title="Overview"
|
||||
type="overview"
|
||||
className="mb-4"
|
||||
description="(Average Values)"
|
||||
widgets={this.getWidgetsByKey(OVERVIEW)}
|
||||
>
|
||||
<div className="grid grid-cols-4 gap-4" ref={this.list[OVERVIEW]}>
|
||||
<OverviewWidgets isOverview />
|
||||
</div>
|
||||
|
|
@ -217,8 +228,9 @@ export default class Dashboard extends React.PureComponent {
|
|||
|
||||
<WidgetSection
|
||||
title="Custom Metrics"
|
||||
type="customMetrics"
|
||||
type={CUSTOM_METRICS}
|
||||
className="mb-4"
|
||||
widgets={[]}
|
||||
description={
|
||||
<div className="flex items-center">
|
||||
{comparing && (
|
||||
|
|
@ -236,7 +248,7 @@ export default class Dashboard extends React.PureComponent {
|
|||
</div>
|
||||
</WidgetSection>
|
||||
|
||||
<WidgetSection title="Errors" className="mb-4" type="errors">
|
||||
<WidgetSection title="Errors" className="mb-4" type="errors" widgets={this.getWidgetsByKey(ERRORS_N_CRASHES)}>
|
||||
<div className={ cn("gap-4", { 'grid grid-cols-2' : !comparing })} ref={this.list[ERRORS_N_CRASHES]}>
|
||||
{ dashboardAppearance.impactedSessionsByJsErrors && <WidgetHolder Component={SessionsAffectedByJSErrors} /> }
|
||||
{ dashboardAppearance.errorsPerDomains && <WidgetHolder Component={ErrorsPerDomain} /> }
|
||||
|
|
@ -250,7 +262,7 @@ export default class Dashboard extends React.PureComponent {
|
|||
</div>
|
||||
</WidgetSection>
|
||||
|
||||
<WidgetSection title="Performance" type="performance" className="mb-4">
|
||||
<WidgetSection title="Performance" type="performance" className="mb-4" widgets={this.getWidgetsByKey(PERFORMANCE)}>
|
||||
<div className={ cn("gap-4", { 'grid grid-cols-2' : !comparing })} ref={this.list[PERFORMANCE]}>
|
||||
{ dashboardAppearance.speedLocation && <WidgetHolder Component={SpeedIndexLocation} /> }
|
||||
{ dashboardAppearance.crashes && <WidgetHolder Component={Crashes} /> }
|
||||
|
|
@ -270,7 +282,7 @@ export default class Dashboard extends React.PureComponent {
|
|||
</div>
|
||||
</WidgetSection>
|
||||
|
||||
<WidgetSection title="Resources" type="resources" className="mb-4">
|
||||
<WidgetSection title="Resources" type="resources" className="mb-4" widgets={this.getWidgetsByKey(RESOURCES)}>
|
||||
<div className={ cn("gap-4", { 'grid grid-cols-2' : !comparing })} ref={this.list[RESOURCES]}>
|
||||
{ dashboardAppearance.resourcesCountByType && <WidgetHolder Component={BreakdownOfLoadedResources} /> }
|
||||
{ dashboardAppearance.resourcesLoadingTime && <WidgetHolder Component={ResourceLoadingTime} /> }
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ function WidgetSection({ className, title, children, description, type }) {
|
|||
<div className={cn(className, 'rounded p-4 bg-gray-light-shade')}>
|
||||
<div className="mb-4 flex items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="text-2xl mr-3">{title}</div>
|
||||
<AddWidgets type={type} />
|
||||
<div className="text-2xl mr-3">{title}</div>
|
||||
{/* <AddWidgets type={type} /> */}
|
||||
</div>
|
||||
{description && <div className="ml-auto color-gray-darkest font-medium text-sm">{description}</div> }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { LineChart, Line, Legend } from 'recharts';
|
|||
import { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
import stl from './CustomMetricWidget.css';
|
||||
import { getChartFormatter, getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper';
|
||||
import { edit, remove, setAlertMetricId, setActiveWidget } from 'Duck/customMetrics';
|
||||
import { edit, remove, setAlertMetricId, setActiveWidget, updateActiveState } from 'Duck/customMetrics';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import APIClient from 'App/api_client';
|
||||
import { setShowAlerts } from 'Duck/dashboard';
|
||||
|
|
@ -37,6 +37,7 @@ interface Props {
|
|||
onAlertClick: (e) => void;
|
||||
edit: (setDefault?) => void;
|
||||
setActiveWidget: (widget) => void;
|
||||
updateActiveState: (metricId, state) => void;
|
||||
}
|
||||
function CustomMetricWidget(props: Props) {
|
||||
const { metric, showSync, compare, period } = props;
|
||||
|
|
@ -89,6 +90,10 @@ function CustomMetricWidget(props: Props) {
|
|||
props.setActiveWidget({ widget: metric, startTimestamp, endTimestamp, timestamp: event.activePayload[0].payload.timestamp, index })
|
||||
}
|
||||
|
||||
const updateActiveState = (metricId, state) => {
|
||||
props.updateActiveState(metricId, state);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={stl.wrapper}>
|
||||
<div className="flex items-center mb-10 p-2">
|
||||
|
|
@ -96,7 +101,7 @@ function CustomMetricWidget(props: Props) {
|
|||
<div className="ml-auto flex items-center">
|
||||
<WidgetIcon className="cursor-pointer mr-6" icon="bell-plus" tooltip="Set Alert" onClick={props.onAlertClick} />
|
||||
<WidgetIcon className="cursor-pointer mr-6" icon="pencil" tooltip="Edit Metric" onClick={() => props.edit(metric)} />
|
||||
<WidgetIcon className="cursor-pointer" icon="close" tooltip="Hide Metric" onClick={deleteHandler} />
|
||||
<WidgetIcon className="cursor-pointer" icon="close" tooltip="Hide Metric" onClick={() => updateActiveState(metric.metricId, false)} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -159,7 +164,7 @@ function CustomMetricWidget(props: Props) {
|
|||
|
||||
export default connect(state => ({
|
||||
period: state.getIn(['dashboard', 'period']),
|
||||
}), { remove, setShowAlerts, setAlertMetricId, edit, setActiveWidget })(CustomMetricWidget);
|
||||
}), { remove, setShowAlerts, setAlertMetricId, edit, setActiveWidget, updateActiveState })(CustomMetricWidget);
|
||||
|
||||
|
||||
const WidgetIcon = ({ className = '', tooltip = '', icon, onClick }) => (
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ function CustomMetricsWidgets(props: Props) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{list.map((item: any) => (
|
||||
{list.filter(item => item.active).map((item: any) => (
|
||||
<CustomMetricWidget
|
||||
metric={item}
|
||||
onClickEdit={props.onClickEdit}
|
||||
|
|
|
|||
|
|
@ -6,34 +6,68 @@ import { updateAppearance } from 'Duck/user';
|
|||
import { WIDGET_LIST } from 'Types/dashboard';
|
||||
import stl from './addWidgets.css';
|
||||
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
|
||||
import { updateActiveState } from 'Duck/customMetrics';
|
||||
|
||||
const CUSTOM_METRICS = 'custom_metrics';
|
||||
|
||||
@connect(state => ({
|
||||
appearance: state.getIn([ 'user', 'account', 'appearance' ]),
|
||||
customMetrics: state.getIn(['customMetrics', 'list']),
|
||||
}), {
|
||||
updateAppearance,
|
||||
updateAppearance, updateActiveState,
|
||||
})
|
||||
@withToggle()
|
||||
export default class AddWidgets extends React.PureComponent {
|
||||
makeAddHandler = widgetKey => () => {
|
||||
const { appearance } = this.props;
|
||||
const newAppearance = appearance.setIn([ 'dashboard', widgetKey ], true);
|
||||
if (this.props.type === CUSTOM_METRICS) {
|
||||
this.props.updateActiveState(widgetKey, true);
|
||||
} else {
|
||||
const { appearance } = this.props;
|
||||
const newAppearance = appearance.setIn([ 'dashboard', widgetKey ], true);
|
||||
this.props.updateAppearance(newAppearance)
|
||||
}
|
||||
|
||||
this.props.switchOpen(false);
|
||||
this.props.updateAppearance(newAppearance)
|
||||
}
|
||||
|
||||
getCustomMetricWidgets = () => {
|
||||
return this.props.customMetrics.filter(i => !i.active).map(item => ({
|
||||
type: CUSTOM_METRICS,
|
||||
key: item.metricId,
|
||||
name: item.name,
|
||||
}))
|
||||
}
|
||||
|
||||
render() {
|
||||
const { appearance, disabled } = this.props;
|
||||
const avaliableWidgets = WIDGET_LIST.filter(({ key, type }) => !appearance.dashboard[ key ] && type === this.props.type );
|
||||
const { disabled, widgets, type } = this.props;
|
||||
// const widgets = WIDGET_LIST.filter(({ key, type }) => !appearance.dashboard[ key ] && type === this.props.type );
|
||||
const filteredWidgets = type === CUSTOM_METRICS ? this.getCustomMetricWidgets() : widgets;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Popup
|
||||
trigger={
|
||||
<IconButton
|
||||
circle
|
||||
size="small"
|
||||
icon="plus"
|
||||
outline
|
||||
onClick={ this.props.switchOpen }
|
||||
disabled={ filteredWidgets.length === 0 } //TODO disabled after Custom fields filtering
|
||||
/>
|
||||
}
|
||||
content={ `Add a metric to this section.` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
<OutsideClickDetectingDiv onClickOutside={() => this.props.switchOpen(false)}>
|
||||
{this.props.open &&
|
||||
<div
|
||||
className={cn(stl.menuWrapper, 'absolute border rounded z-10 bg-white w-auto')}
|
||||
style={{ minWidth: '200px', top: '30px'}}
|
||||
>
|
||||
{avaliableWidgets.map(w => (
|
||||
{filteredWidgets.map(w => (
|
||||
<div
|
||||
className={cn(stl.menuItem, 'whitespace-pre cursor-pointer')}
|
||||
onClick={this.makeAddHandler(w.key)}
|
||||
|
|
@ -44,54 +78,6 @@ export default class AddWidgets extends React.PureComponent {
|
|||
</div>
|
||||
}
|
||||
</OutsideClickDetectingDiv>
|
||||
|
||||
<SlideModal
|
||||
title="Add Widget"
|
||||
size="middle"
|
||||
isDisplayed={ false }
|
||||
content={ this.props.open &&
|
||||
<NoContent
|
||||
title="No Widgets Left"
|
||||
size="small"
|
||||
show={ avaliableWidgets.length === 0}
|
||||
>
|
||||
{ avaliableWidgets.map(({ key, name, description, thumb }) => (
|
||||
<div className={ cn(stl.widgetCard) } key={ key } >
|
||||
<div className="flex justify-between items-center flex-1">
|
||||
<div className="mr-10">
|
||||
<h4>{ name }</h4>
|
||||
</div>
|
||||
<IconButton
|
||||
className="flex-shrink-0"
|
||||
onClick={ this.makeAddHandler(key) }
|
||||
circle
|
||||
outline
|
||||
icon="plus"
|
||||
style={{ width: '24px', height: '24px'}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</NoContent>
|
||||
}
|
||||
onClose={ this.props.switchOpen }
|
||||
/>
|
||||
<Popup
|
||||
trigger={
|
||||
<IconButton
|
||||
circle
|
||||
size="small"
|
||||
icon="plus"
|
||||
outline
|
||||
onClick={ this.props.switchOpen }
|
||||
disabled={ disabled || avaliableWidgets.length === 0 } //TODO disabled after Custom fields filtering
|
||||
/>
|
||||
}
|
||||
content={ `Add a metric to this section.` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top center"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import React from 'react'
|
|||
import cn from 'classnames'
|
||||
import AddWidgets from '../AddWidgets';
|
||||
|
||||
function WidgetSection({ className, title, children, description, type }) {
|
||||
function WidgetSection({ className, title, children, description, type, widgets = [] }) {
|
||||
return (
|
||||
<div className={cn(className, 'rounded p-4 bg-gray-light-shade')}>
|
||||
<div className="mb-4 flex items-center">
|
||||
<div className="flex items-center">
|
||||
<div className="text-2xl mr-3">{title}</div>
|
||||
<AddWidgets type={type} />
|
||||
<AddWidgets type={type} widgets={widgets} />
|
||||
</div>
|
||||
{description && <div className="ml-auto color-gray-darkest font-medium text-sm">{description}</div> }
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const FETCH_LIST = fetchListType(name);
|
|||
const FETCH_SESSION_LIST = fetchListType(`${name}/FETCH_SESSION_LIST`);
|
||||
const FETCH = fetchType(name);
|
||||
const SAVE = saveType(name);
|
||||
const UPDATE_ACTIVE_STATE = saveType(`${name}/UPDATE_ACTIVE_STATE`);
|
||||
const EDIT = editType(name);
|
||||
const INIT = `${name}/INIT`;
|
||||
const SET_ACTIVE_WIDGET = `${name}/SET_ACTIVE_WIDGET`;
|
||||
|
|
@ -72,6 +73,8 @@ function reducer(state = initialState, action = {}) {
|
|||
case success(FETCH_LIST):
|
||||
const { data } = action;
|
||||
return state.set("list", List(data.map(CustomMetric)));
|
||||
// case success(UPDATE_ACTIVE_STATE):
|
||||
// return updateItemInList(updateInstance(state, action.data), action.data);
|
||||
case success(FETCH_SESSION_LIST):
|
||||
return state.set("sessionList", List(action.data.map(item => ({ ...item, sessions: item.sessions.map(Session) }))));
|
||||
case SET_ACTIVE_WIDGET:
|
||||
|
|
@ -161,4 +164,14 @@ export const setActiveWidget = (widget) => (dispatch, getState) => {
|
|||
type: SET_ACTIVE_WIDGET,
|
||||
widget,
|
||||
});
|
||||
}
|
||||
|
||||
export const updateActiveState = (metricId, state) => (dispatch, getState) => {
|
||||
return dispatch({
|
||||
types: UPDATE_ACTIVE_STATE.array,
|
||||
call: client => client.post(`/custom_metrics/${metricId}/status`, { active: state }),
|
||||
metricId
|
||||
}).then(() => {
|
||||
dispatch(fetchList());
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue