Merge pull request #953 from openreplay/ui-ee-tooltip
change(ui) - ee tooltips
This commit is contained in:
commit
14f79832ae
12 changed files with 205 additions and 140 deletions
|
|
@ -21,7 +21,7 @@ function Assist(props: Props) {
|
|||
const redirect = (path: string) => {
|
||||
history.push(withSiteId(path, siteId));
|
||||
};
|
||||
if (isEnterprise) {
|
||||
// if (isEnterprise) {
|
||||
return (
|
||||
<div className="page-margin container-90 flex relative">
|
||||
<div className="flex-1 flex">
|
||||
|
|
@ -39,6 +39,8 @@ function Assist(props: Props) {
|
|||
title="Recordings"
|
||||
iconName="record-circle"
|
||||
onClick={() => redirect(recordings())}
|
||||
disabled={!isEnterprise}
|
||||
tooltipTitle="This feature requires an enterprise license."
|
||||
/>
|
||||
</div>
|
||||
<div className="side-menu-margined w-full">
|
||||
|
|
@ -47,13 +49,13 @@ function Assist(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="page-margin container-90 flex relative">
|
||||
<AssistRouter />
|
||||
</div>
|
||||
)
|
||||
// return (
|
||||
// <div className="page-margin container-90 flex relative">
|
||||
// <AssistRouter />
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
const Cont = connect((state: any) => ({
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ function AssistActions({
|
|||
)}
|
||||
|
||||
{/* @ts-ignore wtf? */}
|
||||
{isEnterprise ? <ScreenRecorder /> : null}
|
||||
<ScreenRecorder />
|
||||
<div className={stl.divider} />
|
||||
|
||||
{/* @ts-ignore */}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,8 @@ function DashboardOptions(props: Props) {
|
|||
{ icon: 'text-paragraph', text: `${!isTitlePresent ? 'Add' : 'Edit'} Description`, onClick: () => editHandler(false) },
|
||||
{ icon: 'users', text: 'Visibility & Access', onClick: editHandler },
|
||||
{ icon: 'trash', text: 'Delete', onClick: deleteHandler },
|
||||
{ icon: 'pdf-download', text: 'Download Report', onClick: renderReport, disabled: !isEnterprise, tooltipTitle: 'This feature requires an enterprise license.' }
|
||||
]
|
||||
if (isEnterprise) {
|
||||
menuItems.unshift({ icon: 'pdf-download', text: 'Download Report', onClick: renderReport });
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemMenu
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { IconNames } from 'App/components/ui/SVG';
|
||||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import cn from 'classnames';
|
||||
|
||||
export interface MetricType {
|
||||
title: string;
|
||||
icon?: IconNames;
|
||||
description: string;
|
||||
slug: string;
|
||||
disabled?: boolean;
|
||||
tooltipTitle?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
@ -16,23 +19,28 @@ interface Props {
|
|||
|
||||
function MetricTypeItem(props: Props) {
|
||||
const {
|
||||
metric: { title, icon, description, slug },
|
||||
metric: { title, icon, description, slug, disabled },
|
||||
onClick = () => {},
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
className="rounded color-gray-darkest flex items-start border border-transparent p-4 hover:bg-active-blue hover:!border-active-blue-border cursor-pointer group hover-color-teal"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="pr-4 pt-1">
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={icon} size="20" color="gray-dark" />
|
||||
<Tooltip disabled={!disabled} title="This feature requires an enterprise license." delay={0}>
|
||||
<div
|
||||
className={cn(
|
||||
'rounded color-gray-darkest flex items-start border border-transparent p-4 hover:bg-active-blue hover:!border-active-blue-border cursor-pointer group hover-color-teal',
|
||||
{ 'opacity-30 pointer-events-none': disabled }
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="pr-4 pt-1">
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={icon} size="20" color="gray-dark" />
|
||||
</div>
|
||||
<div className="flex flex-col items-start text-left">
|
||||
<div className="text-base">{title}</div>
|
||||
<div className="text-sm color-gray-medium font-normal">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-start text-left">
|
||||
<div className="text-base">{title}</div>
|
||||
<div className="text-sm color-gray-medium font-normal">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,27 +2,44 @@ import { useModal } from 'App/components/Modal';
|
|||
import React from 'react';
|
||||
import MetricsLibraryModal from '../MetricsLibraryModal';
|
||||
import MetricTypeItem, { MetricType } from '../MetricTypeItem/MetricTypeItem';
|
||||
import { TYPES, LIBRARY } from 'App/constants/card';
|
||||
import { TYPES, LIBRARY, INSIGHTS } from 'App/constants/card';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { dashboardMetricCreate, withSiteId } from 'App/routes';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
dashboardId: number;
|
||||
siteId: string;
|
||||
isEnterprise: boolean;
|
||||
}
|
||||
function MetricTypeList(props: Props) {
|
||||
const { dashboardId, siteId, history } = props;
|
||||
const { dashboardId, siteId, history, isEnterprise } = props;
|
||||
const { metricStore } = useStore();
|
||||
const { hideModal } = useModal();
|
||||
|
||||
const list = React.useMemo(() => {
|
||||
return TYPES.map((metric: MetricType) => {
|
||||
const disabled = metric.slug === INSIGHTS && !isEnterprise;
|
||||
return {
|
||||
...metric,
|
||||
disabled: metric.slug === INSIGHTS && !isEnterprise,
|
||||
tooltipTitle: disabled ? 'This feature requires an enterprise license.' : '',
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { showModal } = useModal();
|
||||
const onClick = ({ slug }: MetricType) => {
|
||||
hideModal();
|
||||
if (slug === LIBRARY) {
|
||||
return showModal(<MetricsLibraryModal siteId={siteId} dashboardId={dashboardId} />, { right: true, width: 800, onClose: () => {
|
||||
metricStore.updateKey('metricsSearch', '')
|
||||
} });
|
||||
return showModal(<MetricsLibraryModal siteId={siteId} dashboardId={dashboardId} />, {
|
||||
right: true,
|
||||
width: 800,
|
||||
onClose: () => {
|
||||
metricStore.updateKey('metricsSearch', '');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO redirect to card builder with metricType query param
|
||||
|
|
@ -30,17 +47,19 @@ function MetricTypeList(props: Props) {
|
|||
const queryString = new URLSearchParams({ type: slug }).toString();
|
||||
history.push({
|
||||
pathname: path,
|
||||
search: `?${queryString}`
|
||||
search: `?${queryString}`,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{TYPES.map((metric: MetricType) => (
|
||||
{list.map((metric: MetricType) => (
|
||||
<MetricTypeItem metric={metric} onClick={() => onClick(metric)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(MetricTypeList);
|
||||
export default connect((state: any) => ({
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
}))(withRouter(MetricTypeList));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import { Icon, Loader, Button, Link, Input, Form } from 'UI';
|
||||
import { Icon, Loader, Button, Link, Input, Form, Popover, Tooltip } from 'UI';
|
||||
import { login } from 'Duck/user';
|
||||
import { forgotPassword, signup } from 'App/routes';
|
||||
import ReCAPTCHA from 'react-google-recaptcha';
|
||||
|
|
@ -16,12 +16,12 @@ const recaptchaRef = React.createRef();
|
|||
|
||||
@connect(
|
||||
(state, props) => ({
|
||||
errors: state.getIn([ 'user', 'loginRequest', 'errors' ]),
|
||||
loading: state.getIn([ 'user', 'loginRequest', 'loading' ]),
|
||||
errors: state.getIn(['user', 'loginRequest', 'errors']),
|
||||
loading: state.getIn(['user', 'loginRequest', 'loading']),
|
||||
authDetails: state.getIn(['user', 'authDetails']),
|
||||
params: new URLSearchParams(props.location.search)
|
||||
params: new URLSearchParams(props.location.search),
|
||||
}),
|
||||
{ login, setJwt },
|
||||
{ login, setJwt }
|
||||
)
|
||||
@withPageTitle('Login - OpenReplay')
|
||||
@withRouter
|
||||
|
|
@ -34,7 +34,7 @@ export default class Login extends React.Component {
|
|||
|
||||
componentDidMount() {
|
||||
const { params } = this.props;
|
||||
const jwt = params.get('jwt')
|
||||
const jwt = params.get('jwt');
|
||||
if (jwt) {
|
||||
this.props.setJwt(jwt);
|
||||
window.location.href = '/';
|
||||
|
|
@ -44,110 +44,139 @@ export default class Login extends React.Component {
|
|||
handleSubmit = (token) => {
|
||||
const { email, password } = this.state;
|
||||
this.props.login({ email: email.trim(), password, 'g-recaptcha-response': token }).then(() => {
|
||||
const { errors } = this.props;
|
||||
})
|
||||
}
|
||||
const { errors } = this.props;
|
||||
});
|
||||
};
|
||||
|
||||
onSubmit = (e) => {
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const { CAPTCHA_ENABLED } = this.state;
|
||||
if (CAPTCHA_ENABLED && recaptchaRef.current) {
|
||||
recaptchaRef.current.execute();
|
||||
recaptchaRef.current.execute();
|
||||
} else if (!CAPTCHA_ENABLED) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
write = ({ target: { value, name } }) => this.setState({ [ name ]: value })
|
||||
|
||||
|
||||
write = ({ target: { value, name } }) => this.setState({ [name]: value });
|
||||
|
||||
render() {
|
||||
const { errors, loading, authDetails } = this.props;
|
||||
const { CAPTCHA_ENABLED } = this.state;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row" style={{ height: '100vh'}}>
|
||||
<div className={cn("md:w-6/12", stl.left)}>
|
||||
<div className="flex flex-col md:flex-row" style={{ height: '100vh' }}>
|
||||
<div className={cn('md:w-6/12', stl.left)}>
|
||||
<div className="px-6 pt-10">
|
||||
<img src="/assets/logo-white.svg" />
|
||||
</div>
|
||||
<div className="color-white text-lg flex items-center">
|
||||
<div className="flex items-center justify-center w-full" style={{ height: 'calc(100vh - 130px)'}}>
|
||||
<div
|
||||
className="flex items-center justify-center w-full"
|
||||
style={{ height: 'calc(100vh - 130px)' }}
|
||||
>
|
||||
<div className="text-4xl">Welcome Back!</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:w-6/12 flex items-center justify-center py-10">
|
||||
<div className="">
|
||||
<Form onSubmit={ this.onSubmit } className="flex items-center justify-center flex-col">
|
||||
<Form onSubmit={this.onSubmit} className="flex items-center justify-center flex-col">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-center text-3xl mb-6">Login to OpenReplay</h2>
|
||||
{ !authDetails.tenants && <div className="text-center text-xl">Don't have an account? <span className="link"><Link to={ SIGNUP_ROUTE }>Sign up</Link></span></div> }
|
||||
{!authDetails.tenants && (
|
||||
<div className="text-center text-xl">
|
||||
Don't have an account?{' '}
|
||||
<span className="link">
|
||||
<Link to={SIGNUP_ROUTE}>Sign up</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Loader loading={ loading }>
|
||||
{ CAPTCHA_ENABLED && (
|
||||
<Loader loading={loading}>
|
||||
{CAPTCHA_ENABLED && (
|
||||
<ReCAPTCHA
|
||||
ref={ recaptchaRef }
|
||||
ref={recaptchaRef}
|
||||
size="invisible"
|
||||
sitekey={ window.env.CAPTCHA_SITE_KEY }
|
||||
onChange={ token => this.handleSubmit(token) }
|
||||
sitekey={window.env.CAPTCHA_SITE_KEY}
|
||||
onChange={(token) => this.handleSubmit(token)}
|
||||
/>
|
||||
)}
|
||||
<div style={{ width: '350px'}}>
|
||||
)}
|
||||
<div style={{ width: '350px' }}>
|
||||
<div className="mb-6">
|
||||
<label>Email</label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
autoComplete="username"
|
||||
type="text"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
onChange={ this.write }
|
||||
required="true"
|
||||
icon="user-alt"
|
||||
/>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
autoComplete="username"
|
||||
type="text"
|
||||
placeholder="Email"
|
||||
name="email"
|
||||
onChange={this.write}
|
||||
required="true"
|
||||
icon="user-alt"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="mb-2">Password</label>
|
||||
<Input
|
||||
autoComplete="current-password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
onChange={ this.write }
|
||||
required="true"
|
||||
icon="lock-alt"
|
||||
/>
|
||||
<Input
|
||||
autoComplete="current-password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
onChange={this.write}
|
||||
required="true"
|
||||
icon="lock-alt"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Loader>
|
||||
{ errors.length ?
|
||||
(<div className={ stl.errors }>
|
||||
{ errors.map(error => (
|
||||
{errors.length ? (
|
||||
<div className={stl.errors}>
|
||||
{errors.map((error) => (
|
||||
<div className={stl.errorItem}>
|
||||
<Icon name="info" color="red" size="20"/>
|
||||
<span className="color-red ml-2">{ error }<br /></span>
|
||||
<Icon name="info" color="red" size="20" />
|
||||
<span className="color-red ml-2">
|
||||
{error}
|
||||
<br />
|
||||
</span>
|
||||
</div>
|
||||
)) }
|
||||
</div>) : null
|
||||
}
|
||||
{/* <div className={ stl.formFooter }> */}
|
||||
<Button className="mt-2" type="submit" variant="primary" >{ 'Login' }</Button>
|
||||
|
||||
<div className={ cn(stl.links, 'text-lg') }>
|
||||
<Link to={ FORGOT_PASSWORD }>{'Forgot your password?'}</Link>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{/* <div className={ stl.formFooter }> */}
|
||||
<Button className="mt-2" type="submit" variant="primary">
|
||||
{'Login'}
|
||||
</Button>
|
||||
|
||||
<div className={cn(stl.links, 'text-lg')}>
|
||||
<Link to={FORGOT_PASSWORD}>{'Forgot your password?'}</Link>
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</Form>
|
||||
{ authDetails.sso && (
|
||||
<div className={cn(stl.sso, "py-2 flex flex-col items-center")}>
|
||||
<div className="mb-4">or</div>
|
||||
<a href="/api/sso/saml2" rel="noopener noreferrer">
|
||||
<Button variant="outline" type="submit" >{ `Login with SSO (${authDetails.ssoProvider})` }</Button>
|
||||
</a>
|
||||
|
||||
<div className={cn(stl.sso, 'py-2 flex flex-col items-center')}>
|
||||
<div className="mb-4">or</div>
|
||||
|
||||
<div>
|
||||
<Tooltip
|
||||
delay={0}
|
||||
disabled={authDetails.sso}
|
||||
title={<div>This feature requires an enterprise license.</div>}
|
||||
placement="top"
|
||||
// open={true}
|
||||
>
|
||||
<a
|
||||
href="/api/sso/saml2"
|
||||
rel="noopener noreferrer"
|
||||
className={cn({ 'pointer-events-none opacity-30': !authDetails.sso })}
|
||||
>
|
||||
<Button variant="outline" type="submit">
|
||||
{`Login with SSO ${authDetails.ssoProvider ? `(${authDetails.ssoProvider})` : ''}`}
|
||||
</Button>
|
||||
</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,9 +35,11 @@ const supportedMessage = `Supported Browsers: ${supportedBrowsers.join(', ')}`;
|
|||
function ScreenRecorder({
|
||||
siteId,
|
||||
sessionId,
|
||||
isEnterprise,
|
||||
}: {
|
||||
siteId: string;
|
||||
sessionId: string;
|
||||
isEnterprise: boolean;
|
||||
}) {
|
||||
const { player, store } = React.useContext(PlayerContext)
|
||||
const recordingState = store.get().recordingState
|
||||
|
|
@ -93,11 +95,11 @@ function ScreenRecorder({
|
|||
player.assistManager.requestRecording()
|
||||
};
|
||||
|
||||
if (!isSupported()) {
|
||||
if (!isSupported() || !isEnterprise) {
|
||||
return (
|
||||
<div className="p-2">
|
||||
{/* @ts-ignore */}
|
||||
<Tooltip title={supportedMessage}>
|
||||
<Tooltip title={isEnterprise ? supportedMessage : 'This feature requires an enterprise license.'}>
|
||||
<Button icon="record-circle" disabled variant="text-primary">
|
||||
Record Activity
|
||||
</Button>
|
||||
|
|
@ -119,6 +121,7 @@ function ScreenRecorder({
|
|||
}
|
||||
|
||||
export default connect((state: any) => ({
|
||||
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
|
||||
siteId: state.getIn(['site', 'siteId']),
|
||||
sessionId: state.getIn(['sessions', 'current']).sessionId,
|
||||
}))(observer(ScreenRecorder))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Icon, Popover } from 'UI';
|
||||
import { Icon, Popover, Tooltip } from 'UI';
|
||||
import styles from './itemMenu.module.css';
|
||||
import cn from 'classnames';
|
||||
|
||||
|
|
@ -9,6 +9,7 @@ interface Item {
|
|||
onClick: (args: any) => void;
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltipTitle?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
@ -66,23 +67,25 @@ export default class ItemMenu extends React.PureComponent<Props> {
|
|||
>
|
||||
{items
|
||||
.filter(({ hidden }) => !hidden)
|
||||
.map(({ onClick, text, icon, disabled = false }) => (
|
||||
<div
|
||||
key={text}
|
||||
onClick={!disabled ? this.onClick(onClick) : () => {}}
|
||||
className={disabled ? 'cursor-not-allowed' : ''}
|
||||
role="menuitem"
|
||||
>
|
||||
<div className={cn(styles.menuItem, 'text-neutral-700', { disabled: disabled })}>
|
||||
{icon && (
|
||||
<div className={styles.iconWrapper}>
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={icon} size="13" color="gray-dark" />
|
||||
</div>
|
||||
)}
|
||||
<div>{text}</div>
|
||||
.map(({ onClick, text, icon, disabled = false, tooltipTitle = '' }) => (
|
||||
<Tooltip disabled={!disabled} title={tooltipTitle}>
|
||||
<div
|
||||
key={text}
|
||||
onClick={!disabled ? this.onClick(onClick) : () => {}}
|
||||
className={disabled ? 'cursor-not-allowed' : ''}
|
||||
role="menuitem"
|
||||
>
|
||||
<div className={cn(styles.menuItem, 'text-neutral-700', { disabled: disabled })}>
|
||||
{icon && (
|
||||
<div className={styles.iconWrapper}>
|
||||
{/* @ts-ignore */}
|
||||
<Icon name={icon} size="13" color="gray-dark" />
|
||||
</div>
|
||||
)}
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -90,7 +93,7 @@ export default class ItemMenu extends React.PureComponent<Props> {
|
|||
<div
|
||||
// onClick={this.toggleMenu}
|
||||
className={cn(
|
||||
'flex items-center cursor-pointer select-none hover rounded-full',
|
||||
'flex items-center cursor-pointer select-none hover',
|
||||
!this.props.flat ? parentStyles : '',
|
||||
{ 'bg-gray-light': !this.props.flat && displayed && label }
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
}
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
/* border-radius: 18px; */
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.2s;
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ function SideMenuitem({
|
|||
title,
|
||||
active = false,
|
||||
disabled = false,
|
||||
tooltipTitle = '',
|
||||
onClick,
|
||||
deleteHandler = null,
|
||||
leading = null,
|
||||
|
|
@ -20,8 +21,8 @@ function SideMenuitem({
|
|||
return (
|
||||
<Tooltip
|
||||
disabled={ !disabled }
|
||||
title={ 'No recordings' }
|
||||
placement="left"
|
||||
title={ tooltipTitle }
|
||||
placement="top"
|
||||
>
|
||||
<div
|
||||
className={ cn(
|
||||
|
|
|
|||
|
|
@ -211,24 +211,24 @@ export const TYPES: CardType[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'User Path',
|
||||
icon: 'signpost-split',
|
||||
description: 'Discover user journeys between 2 points.',
|
||||
slug: USER_PATH,
|
||||
},
|
||||
{
|
||||
title: 'Retention',
|
||||
icon: 'arrow-repeat',
|
||||
description: 'Retension graph of users / features over a period of time.',
|
||||
slug: RETENTION,
|
||||
},
|
||||
{
|
||||
title: 'Feature Adoption',
|
||||
icon: 'card-checklist',
|
||||
description: 'Find the adoption of your all features in your app.',
|
||||
slug: FEATURE_ADOPTION,
|
||||
},
|
||||
// {
|
||||
// title: 'User Path',
|
||||
// icon: 'signpost-split',
|
||||
// description: 'Discover user journeys between 2 points.',
|
||||
// slug: USER_PATH,
|
||||
// },
|
||||
// {
|
||||
// title: 'Retention',
|
||||
// icon: 'arrow-repeat',
|
||||
// description: 'Retension graph of users / features over a period of time.',
|
||||
// slug: RETENTION,
|
||||
// },
|
||||
// {
|
||||
// title: 'Feature Adoption',
|
||||
// icon: 'card-checklist',
|
||||
// description: 'Find the adoption of your all features in your app.',
|
||||
// slug: FEATURE_ADOPTION,
|
||||
// },
|
||||
{
|
||||
title: 'Insights',
|
||||
icon: 'lightbulb',
|
||||
|
|
|
|||
|
|
@ -212,6 +212,8 @@ export default class Widget {
|
|||
);
|
||||
} else {
|
||||
if (data.hasOwnProperty('chart')) {
|
||||
_data['value'] = data.value;
|
||||
_data['unit'] = data.unit;
|
||||
_data['chart'] = getChartFormatter(period)(data.chart);
|
||||
_data['namesMap'] = data.chart
|
||||
.map((i: any) => Object.keys(i))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue