change(ui) - remove projects from client, and other fixes

This commit is contained in:
Shekar Siri 2022-04-13 16:37:21 +02:00
parent d50d63ba7e
commit bb6401b675
52 changed files with 265 additions and 212 deletions

View file

@ -22,7 +22,7 @@ const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDeta
import WidgetViewPure from 'Components/Dashboard/components/WidgetView';
import Header from 'Components/Header/Header';
// import ResultsModal from 'Shared/Results/ResultsModal';
import { fetchList as fetchIntegrationVariables } from 'Duck/customField';
import { fetchList as fetchMetadata } from 'Duck/customField';
import { fetchList as fetchSiteList } from 'Duck/site';
import { fetchList as fetchAnnouncements } from 'Duck/announcements';
import { fetchList as fetchAlerts } from 'Duck/alerts';
@ -80,7 +80,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB);
@withStore
@withRouter
@connect((state) => {
const siteId = state.getIn([ 'user', 'siteId' ]);
const siteId = state.getIn([ 'site', 'siteId' ]);
const jwt = state.get('jwt');
const changePassword = state.getIn([ 'user', 'account', 'changePassword' ]);
const userInfoLoading = state.getIn([ 'user', 'fetchUserInfoRequest', 'loading' ]);
@ -88,7 +88,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB);
jwt,
siteId,
changePassword,
sites: state.getIn([ 'user', 'client', 'sites' ]),
sites: state.getIn([ 'site', 'list' ]),
isLoggedIn: jwt !== null && !changePassword,
loading: siteId === null || userInfoLoading,
email: state.getIn([ 'user', 'account', 'email' ]),
@ -103,7 +103,7 @@ const ONBOARDING_REDIRECT_PATH = routes.onboarding(OB_DEFAULT_TAB);
fetchUserInfo,
fetchTenants,
setSessionPath,
fetchIntegrationVariables,
fetchMetadata,
fetchSiteList,
fetchAnnouncements,
fetchAlerts,
@ -124,17 +124,18 @@ class Router extends React.Component {
fetchInitialData = () => {
Promise.all([
this.props.fetchUserInfo().then(() => {
const { mstore } = this.props
mstore.initClient();
this.props.fetchIntegrationVariables()
}),
this.props.fetchSiteList().then(() => {
setTimeout(() => {
this.props.fetchAnnouncements();
this.props.fetchAlerts();
this.props.fetchWatchdogStatus();
}, 100);
}),
this.props.fetchSiteList().then(() => {
const { mstore } = this.props
mstore.initClient();
setTimeout(() => {
this.props.fetchMetadata()
this.props.fetchAnnouncements();
this.props.fetchAlerts();
this.props.fetchWatchdogStatus();
}, 100);
})
})
])
}
@ -197,25 +198,17 @@ class Router extends React.Component {
{ onboarding &&
<Redirect to={ withSiteId(ONBOARDING_REDIRECT_PATH, siteId)} />
}
{ siteIdList.length === 0 &&
{/* { siteIdList.length === 0 &&
<Redirect to={ routes.client(routes.CLIENT_TABS.SITES) } />
}
} */}
{/* DASHBOARD and Metrics */}
<Route exact strict path={ withSiteId(METRICS_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(METRICS_DETAILS, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_SELECT_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_METRIC_CREATE_PATH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(DASHBOARD_METRIC_DETAILS_PATH, siteIdList) } component={ Dashboard } />
{/* <Route exact strict path={ withSiteId(WIDGET_PATAH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(WIDGET_PATAH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(WIDGET_PATAH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(WIDGET_PATAH, siteIdList) } component={ Dashboard } />
<Route exact strict path={ withSiteId(WIDGET_PATAH, siteIdList) } component={ Dashboard } /> */}
<Route exact strict path={ withSiteId(ASSIST_PATH, siteIdList) } component={ Assist } />
<Route exact strict path={ withSiteId(ERRORS_PATH, siteIdList) } component={ Errors } />

View file

@ -56,7 +56,7 @@ export const clean = (obj, forbidenValues = [ undefined, '' ]) => {
export default class APIClient {
constructor() {
const jwt = store.getState().get('jwt');
const siteId = store.getState().getIn([ 'user', 'siteId' ]);
const siteId = store.getState().getIn([ 'site', 'siteId' ]);
this.init = {
headers: {
Accept: 'application/json',

View file

@ -96,6 +96,6 @@ class Announcements extends React.Component {
export default connect(state => ({
announcements: state.getIn(['announcements', 'list']),
loading: state.getIn(['announcements', 'fetchList', 'loading']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
sites: state.getIn([ 'site', 'list' ]),
}), { fetchList, setLastRead })(Announcements);

View file

@ -53,7 +53,7 @@ const allowedQueryKeys = [
sources: state.getIn([ 'customFields', 'sources' ]),
filterValues: state.get('filterValues'),
favoriteList: state.getIn([ 'sessions', 'favoriteList' ]),
currentProjectId: state.getIn([ 'user', 'siteId' ]),
currentProjectId: state.getIn([ 'site', 'siteId' ]),
sites: state.getIn([ 'site', 'list' ]),
watchdogs: state.getIn(['watchdogs', 'list']),
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),

View file

@ -72,7 +72,7 @@ const SessionCaptureRate = props => {
}
export default connect(state => ({
currentProjectId: state.getIn([ 'user', 'siteId' ]),
currentProjectId: state.getIn([ 'site', 'siteId' ]),
captureRate: state.getIn(['watchdogs', 'captureRate']),
loading: state.getIn(['watchdogs', 'savingCaptureRate', 'loading']),
}), {

View file

@ -13,7 +13,7 @@ import { confirm } from 'UI/Confirmation';
fields: state.getIn(['customFields', 'list']).sortBy(i => i.index),
field: state.getIn(['customFields', 'instance']),
loading: state.getIn(['customFields', 'fetchRequest', 'loading']),
sites: state.getIn([ 'user', 'client', 'sites' ]),
sites: state.getIn([ 'site', 'list' ]),
errors: state.getIn([ 'customFields', 'saveRequest', 'errors' ]),
}), {
init,

View file

@ -4,8 +4,8 @@ import SiteDropdown from 'Shared/SiteDropdown';
import { save, init, edit, remove, fetchList } from 'Duck/integrations/actions';
@connect((state, { name, customPath }) => ({
sites: state.getIn([ 'user', 'client', 'sites' ]),
initialSiteId: state.getIn([ 'user', 'siteId' ]),
sites: state.getIn([ 'site', 'list' ]),
initialSiteId: state.getIn([ 'site', 'siteId' ]),
list: state.getIn([ name, 'list' ]),
config: state.getIn([ name, 'instance']),
saving: state.getIn([ customPath || name, 'saveRequest', 'loading']),

View file

@ -1,7 +1,8 @@
import { connect } from 'react-redux';
import { Input, Button, Label } from 'UI';
import { save, edit, update , fetchList } from 'Duck/site';
import { pushNewSite, setSiteId } from 'Duck/user';
import { pushNewSite } from 'Duck/user';
import { setSiteId } from 'Duck/site';
import { withRouter } from 'react-router-dom';
import styles from './siteForm.css';

View file

@ -59,7 +59,7 @@ class Webhooks extends React.PureComponent {
title="No webhooks available."
size="small"
show={ noSlackWebhooks.size === 0 }
icon
animatedIcon="no-results"
>
<div className={ styles.list }>
{ noSlackWebhooks.map(webhook => (

View file

@ -18,7 +18,7 @@
& .tabContent {
background-color: white;
padding: 25px;
margin-top: -30px;
/* margin-top: -30px; */
margin-right: -20px;
width: 100%;
}

View file

@ -35,6 +35,6 @@ const DashboardHeader = props => {
export default connect(state => ({
period: state.getIn([ 'dashboard', 'period' ]),
platform: state.getIn([ 'dashboard', 'platform' ]),
currentProjectId: state.getIn([ 'user', 'siteId' ]),
currentProjectId: state.getIn([ 'site', 'siteId' ]),
sites: state.getIn([ 'site', 'list' ]),
}), { setPeriod, setPlatform })(DashboardHeader)

View file

@ -3,18 +3,16 @@ import withPageTitle from 'HOCs/withPageTitle';
import { observer, useObserver } from "mobx-react-lite";
import { useStore } from 'App/mstore';
import { withRouter } from 'react-router-dom';
import {
dashboardSelected,
withSiteId,
} from 'App/routes';
import DashboardSideMenu from './components/DashboardSideMenu';
import { Loader } from 'UI';
import DashboardRouter from './components/DashboardRouter';
import cn from 'classnames';
function NewDashboard(props) {
const { history, match: { params: { siteId, dashboardId, metricId } } } = props;
const { dashboardStore } = useStore();
const loading = useObserver(() => dashboardStore.isLoading);
const isMetricDetails = history.location.pathname.includes('/metrics/') || history.location.pathname.includes('/metric/');
useEffect(() => {
dashboardStore.fetchList().then((resp) => {
@ -32,20 +30,18 @@ function NewDashboard(props) {
});
}, [siteId]);
return (
return useObserver(() => (
<Loader loading={loading}>
<div className="page-margin container-90">
<div className="side-menu">
<div className={cn("side-menu", { 'hidden' : isMetricDetails })}>
<DashboardSideMenu siteId={siteId} />
</div>
<div className="side-menu-margined">
<div className={cn({ "side-menu-margined" : !isMetricDetails, "container-70" : isMetricDetails })}>
<DashboardRouter siteId={siteId} />
</div>
</div>
</Loader>
);
));
}
export default withPageTitle('New Dashboard')(
withRouter(observer(NewDashboard))
);
export default withPageTitle('New Dashboard')(withRouter(NewDashboard));

View file

@ -41,5 +41,5 @@ function SideMenuSection({ title, items, onItemClick, setShowAlerts, siteId }) {
SideMenuSection.displayName = "SideMenuSection";
export default connect(state => ({
siteId: state.getIn([ 'user', 'siteId' ])
siteId: state.getIn([ 'site', 'siteId' ])
}), { setShowAlerts })(SideMenuSection);

View file

@ -6,16 +6,17 @@ import { LineChart, Line, Legend } from 'recharts';
interface Props {
data: any;
params: any;
seriesMap: any;
// seriesMap: any;
colors: any;
onClick?: (event, index) => void;
}
function CustomMetriLineChart(props: Props) {
const { data, params, seriesMap = [], colors, onClick = () => null } = props;
const { data = { chart: [], namesMap: [] }, params, colors, onClick = () => null } = props;
return (
<ResponsiveContainer height={ 240 } width="100%">
<LineChart
data={ data }
data={ data.chart }
margin={Styles.chartMargins}
// syncId={ showSync ? "domainsErrors_4xx" : undefined }
onClick={onClick}
@ -37,18 +38,18 @@ function CustomMetriLineChart(props: Props) {
/>
<Legend />
<Tooltip {...Styles.tooltip} />
{ seriesMap.map((key, index) => (
{ Array.isArray(data.namesMap) && data.namesMap.map((key, index) => (
<Line
key={key}
name={key}
type="monotone"
dataKey={key}
stroke={colors[index]}
fillOpacity={ 1 }
strokeWidth={ 2 }
strokeOpacity={ 0.6 }
// fill="url(#colorCount)"
dot={false}
key={key}
name={key}
type="monotone"
dataKey={key}
stroke={colors[index]}
fillOpacity={ 1 }
strokeWidth={ 2 }
strokeOpacity={ 0.6 }
// fill="url(#colorCount)"
dot={false}
/>
))}
</LineChart>

View file

@ -16,24 +16,24 @@ function CustomMetricOverviewChart(props: Props) {
return (
<div className="relative -mx-4">
<div className="absolute flex items-start flex-col justify-center inset-0 p-3">
<div className="absolute flex items-start flex-col justify-start inset-0 p-3">
<div className="mb-2 flex items-center" >
</div>
<div className="flex items-center">
<CountBadge
// title={subtext}
count={ countView(Math.round(data.value), data.unit) }
change={ data.progress || 0 }
unit={ data.unit }
// className={textClass}
/>
<CountBadge
// title={subtext}
count={ countView(Math.round(data.value), data.unit) }
change={ data.progress || 0 }
unit={ data.unit }
// className={textClass}
/>
</div>
</div>
<ResponsiveContainer height={ 100 } width="100%">
<AreaChart
data={ data.chart }
margin={ {
top: 85, right: 0, left: 0, bottom: 5,
top: 50, right: 0, left: 0, bottom: 5,
} }
>
{gradientDef}

View file

@ -12,7 +12,7 @@ function CustomMetriPercentage(props: Props) {
return (
<div className="flex flex-col items-center justify-center" style={{ height: '240px'}}>
<div className="text-6xl">{numberWithCommas(data.count)}</div>
<div className="text-lg mt-6">{`${parseInt(data.previousCount).toFixed(1)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}</div>
<div className="text-lg mt-6">{`${parseInt(data.previousCount)} ( ${parseInt(data.countProgress).toFixed(1)}% )`}</div>
<div className="color-gray-medium">from previous period.</div>
</div>
)

View file

@ -136,7 +136,7 @@ function CustomMetricWidget(props: Props) {
<CustomMetriLineChart
data={ data }
params={ params }
seriesMap={ seriesMap }
// seriesMap={ seriesMap }
colors={ colors }
onClick={ clickHandler }
/>

View file

@ -168,7 +168,7 @@ function CustomMetricWidget(props: Props) {
{ metric.viewType === 'lineChart' && (
<CustomMetriLineChart
data={data}
seriesMap={seriesMap}
// seriesMap={seriesMap}
colors={colors}
params={params}
/>

View file

@ -40,7 +40,7 @@ function CPULoad(props: Props) {
name="Avg"
type="monotone"
unit="%"
dataKey="avgCpu"
dataKey="value"
stroke={Styles.colors[0]}
fillOpacity={ 1 }
strokeWidth={ 2 }

View file

@ -32,14 +32,13 @@ function Crashes(props: Props) {
{...Styles.yaxis}
allowDecimals={false}
tickFormatter={val => Styles.tickFormatter(val)}
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
label={{ ...Styles.axisLabelLeft, value: "Number of Crashes" }}
/>
<Tooltip {...Styles.tooltip} />
<Area
name="Crashes"
type="monotone"
unit="%"
dataKey="avgCpu"
dataKey="avg"
stroke={Styles.colors[0]}
fillOpacity={ 1 }
strokeWidth={ 2 }

View file

@ -59,14 +59,14 @@ function DomBuildingTime(props: Props) {
{...Styles.yaxis}
allowDecimals={false}
tickFormatter={val => Styles.tickFormatter(val)}
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
label={{ ...Styles.axisLabelLeft, value: "DOM Build Time (ms)" }}
/>
<Tooltip {...Styles.tooltip} />
<Area
name="Avg"
type="monotone"
unit="%"
dataKey="avgCpu"
// unit="%"
dataKey="avg"
stroke={Styles.colors[0]}
fillOpacity={ 1 }
strokeWidth={ 2 }

View file

@ -37,13 +37,13 @@ function FPS(props: Props) {
{...Styles.yaxis}
allowDecimals={false}
tickFormatter={val => Styles.tickFormatter(val)}
label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }}
label={{ ...Styles.axisLabelLeft, value: "Frames Per Second" }}
/>
<Tooltip {...Styles.tooltip} />
<Area
name="Avg"
type="monotone"
dataKey="avgFps"
dataKey="avg"
stroke={Styles.colors[0]}
fillOpacity={ 1 }
strokeWidth={ 2 }

View file

@ -44,7 +44,7 @@ function MemoryConsumption(props: Props) {
name="Avg"
unit=" mb"
type="monotone"
dataKey="avgFps"
dataKey="avg"
stroke={Styles.colors[0]}
fillOpacity={ 1 }
strokeWidth={ 2 }

View file

@ -86,7 +86,7 @@ function DashboardView(props: Props) {
</div>
<div className="flex items-center">
<div className="flex items-center">
<span className="mr-2 color-gray-medium">Time Range</span>
{/* <span className="mr-2 color-gray-medium">Time Range</span> */}
<DateRange
rangeValue={period.rangeName}
startDate={period.start}
@ -97,18 +97,21 @@ function DashboardView(props: Props) {
/>
</div>
<div className="mx-4" />
<ItemMenu
items={[
{
text: 'Edit',
onClick: onEdit
},
{
text: 'Delete Dashboard',
onClick: onDelete
},
]}
/>
<div className="flex items-center">
<span className="mr-1 color-gray-medium">More</span>
<ItemMenu
items={[
{
text: 'Edit',
onClick: onEdit
},
{
text: 'Delete Dashboard',
onClick: onDelete
},
]}
/>
</div>
</div>
</div>
<DashboardWidgetGrid

View file

@ -9,9 +9,11 @@ import { Loader } from 'UI';
import { useStore } from 'App/mstore';
import WidgetPredefinedChart from '../WidgetPredefinedChart';
import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart';
import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper';
interface Props {
metric: any;
isWidget?: boolean
onClick?: () => void;
}
function WidgetChart(props: Props) {
const { isWidget = false, metric } = props;
@ -19,12 +21,32 @@ function WidgetChart(props: Props) {
const period = useObserver(() => dashboardStore.period);
const colors = Styles.customMetricColors;
const [loading, setLoading] = useState(false)
const [seriesMap, setSeriesMap] = useState<any>([]);
const params = { density: 28 }
const isOverviewWidget = metric.metricType === 'predefined' && metric.viewType === 'overview';
const params = { density: isOverviewWidget ? 7 : 70 }
const metricParams = { ...params }
const prevMetricRef = useRef<any>();
const [data, setData] = useState<any>(metric.data);
const onChartClick = (event: any) => {
if (event) {
const payload = event.activePayload[0].payload;
const timestamp = payload.timestamp;
const periodTimestamps = metric.metricType === 'timeseries' ?
getStartAndEndTimestampsByDensity(timestamp, period.start, period.end, params.density) :
period.toTimestamps();
// const activeWidget = {
// widget: metric,
// period: period,
// ...periodTimestamps,
// timestamp: payload.timestamp,
// index,
// }
// props.setActiveWidget(activeWidget);
}
}
useEffect(() => {
if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) {
prevMetricRef.current = metric;
@ -33,8 +55,8 @@ function WidgetChart(props: Props) {
prevMetricRef.current = metric;
setLoading(true);
const data = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() };
dashboardStore.fetchMetricChartData(metric, data, isWidget).then((res: any) => {
const payload = isWidget ? { ...params } : { ...metricParams, ...metric.toJson() };
dashboardStore.fetchMetricChartData(metric, payload, isWidget).then((res: any) => {
setData(res);
}).finally(() => {
setLoading(false);
@ -42,10 +64,10 @@ function WidgetChart(props: Props) {
}, [period]);
const renderChart = () => {
const { metricType, viewType, predefinedKey } = metric;
const { metricType, viewType } = metric;
if (metricType === 'predefined') {
if (viewType === 'overview') {
if (isOverviewWidget) {
return <CustomMetricOverviewChart data={data} />
}
return <WidgetPredefinedChart data={data} predefinedKey={metric.predefinedKey} />
@ -55,16 +77,16 @@ function WidgetChart(props: Props) {
if (viewType === 'lineChart') {
return (
<CustomMetriLineChart
data={metric.data}
seriesMap={seriesMap}
data={data}
colors={colors}
params={params}
onClick={onChartClick}
/>
)
} else if (viewType === 'progress') {
return (
<CustomMetricPercentage
data={metric.data[0]}
data={data[0]}
colors={colors}
params={params}
/>
@ -74,12 +96,12 @@ function WidgetChart(props: Props) {
if (metricType === 'table') {
if (viewType === 'table') {
return <CustomMetricTable metric={metric} data={metric.data[0]} />;
return <CustomMetricTable metric={metric} data={data[0]} />;
} else if (viewType === 'pieChart') {
return (
<CustomMetricPieChart
metric={metric}
data={metric.data[0]}
data={data[0]}
colors={colors}
params={params}
/>

View file

@ -11,12 +11,12 @@ interface Props {
function WidgetSessions(props: Props) {
const { className = '' } = props;
const { dashboardStore } = useStore();
const period = useObserver(() => dashboardStore.period);
const filter = useObserver(() => dashboardStore.drillDownFilter);
const widget = dashboardStore.currentWidget;
const range = period.toTimestamps()
const startTime = DateTime.fromMillis(range.startTimestamp).toFormat('LLL dd, yyyy HH:mm a');
const endTime = DateTime.fromMillis(range.endTimestamp).toFormat('LLL dd, yyyy HH:mm a');
// const range = period.toTimestamps()
const startTime = DateTime.fromMillis(filter.startTimestamp).toFormat('LLL dd, yyyy HH:mm a');
const endTime = DateTime.fromMillis(filter.endTimestamp).toFormat('LLL dd, yyyy HH:mm a');
return useObserver(() => (
<div className={cn(className)}>

View file

@ -38,7 +38,7 @@ function WidgetView(props: Props) {
return useObserver(() => (
<Loader loading={loading}>
<div className="relative">
<div className="relative pb-10">
<BackLink onClick={onBackHandler} vertical className="absolute" style={{ left: '-50px', top: '0px' }} />
<div className="bg-white rounded border">
<div className="p-4 flex justify-between items-center">

View file

@ -63,6 +63,7 @@ function WidgetWrapper(props: Props) {
const onChartClick = () => {
if (!isWidget || widget.metricType === 'predefined') return;
props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId));
}

View file

@ -143,7 +143,7 @@ export default connect((state, props) => {
funnelId: props.match.params.funnelId,
activeStages: state.getIn(['funnels', 'activeStages']),
funnelFilters: state.getIn(['funnels', 'funnelFilters']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
liveFilters: state.getIn(['funnelFilters', 'appliedFilter']),
}
}, {

View file

@ -33,5 +33,5 @@ function FunnelDropdown(props) {
export default connect((state, props) => ({
funnels: state.getIn(['funnels', 'list']),
funnel: state.getIn(['funnels', 'instance']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}), { })(withRouter(FunnelDropdown))

View file

@ -39,5 +39,5 @@ export default connect((state, props) => ({
issue: state.getIn(['funnels', 'issue']),
issueId: props.match.params.issueId,
funnelId: props.match.params.funnelId,
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}), { fetchIssue, setNavRef, resetIssue })(withRouter(FunnelIssueDetails))

View file

@ -73,7 +73,7 @@ export default connect(state => ({
list: state.getIn(['funnels', 'issues']),
criticalIssuesCount: state.getIn(['funnels', 'criticalIssuesCount']),
loading: state.getIn(['funnels', 'fetchIssuesRequest', 'loading']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
funnel: state.getIn(['funnels', 'instance']),
activeStages: state.getIn(['funnels', 'activeStages']),
funnelFilters: state.getIn(['funnels', 'funnelFilters']),

View file

@ -28,5 +28,5 @@ function FunnelList(props) {
export default connect(state => ({
list: state.getIn(['funnels', 'list']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}))(withRouter(FunnelList))

View file

@ -151,7 +151,7 @@ export default withRouter(connect(
state => ({
account: state.getIn([ 'user', 'account' ]),
appearance: state.getIn([ 'user', 'account', 'appearance' ]),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
sites: state.getIn([ 'site', 'list' ]),
showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]),
boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ])

View file

@ -37,7 +37,7 @@ const styles = {
};
@connect(state => ({
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
boarding: state.getIn([ 'dashboard', 'boarding' ]),
boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]),
}), {

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { setSiteId } from 'Duck/user';
import { setSiteId } from 'Duck/site';
import { withRouter } from 'react-router-dom';
import { hasSiteId, siteChangeAvaliable } from 'App/routes';
import { STATUS_COLOR_MAP, GREEN } from 'Types/site';
@ -19,7 +19,7 @@ import { withStore } from 'App/mstore'
@withRouter
@connect(state => ({
sites: state.getIn([ 'site', 'list' ]),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
account: state.getIn([ 'user', 'account' ]),
}), {
setSiteId,

View file

@ -66,7 +66,7 @@ function StackEvents({
export default connect(state => ({
hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) ||
!state.getIn([ 'user', 'client', 'sites' ]).some(s => s.stackIntegrations),
!state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations),
}), {
hideHint
})(StackEvents);

View file

@ -52,6 +52,6 @@ function AutoplayTimer({ nextId, siteId, history }) {
export default withRouter(connect(state => ({
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
nextId: parseInt(state.getIn([ 'sessions', 'nextId' ])),
}))(AutoplayTimer))

View file

@ -41,7 +41,7 @@ const ASSIST_ROUTE = assistRoute();
issuesFetched: state.getIn([ 'issues', 'issuesFetched' ]),
local: state.getIn(['sessions', 'timezone']),
funnelRef: state.getIn(['funnels', 'navRef']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
metaList: state.getIn(['customFields', 'list']).map(i => i.key),
closedLive: !!state.getIn([ 'sessions', 'errors' ]) || (isAssist && !session.live),
}

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { connectPlayer } from 'Player';
import { connectPlayer, jump } from 'Player';
import { NoContent, Tabs } from 'UI';
import withEnumToggle from 'HOCs/withEnumToggle';
import { hideHint } from 'Duck/components/player';
@ -18,7 +18,7 @@ const TABS = [ ALL, ...typeList ].map(tab =>({ text: tab, key: tab }));
}))
@connect(state => ({
hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) ||
!state.getIn([ 'user', 'client', 'sites' ]).some(s => s.stackIntegrations),
!state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations),
}), {
hideHint
})
@ -66,7 +66,11 @@ export default class StackEvents extends React.PureComponent {
>
<Autoscroll>
{ filteredStackEvents.map(userEvent => (
<UserEvent key={ userEvent.key } userEvent={ userEvent }/>
<UserEvent
key={ userEvent.key }
userEvent={ userEvent }
onJump={ () => jump(userEvent.time) }
/>
))}
</Autoscroll>
</NoContent>

View file

@ -1,6 +1,6 @@
import cn from 'classnames';
import { OPENREPLAY, SENTRY, DATADOG, STACKDRIVER } from 'Types/session/stackEvent';
import { Modal, Icon, SlideModal } from 'UI';
import { Modal, Icon, SlideModal, IconButton } from 'UI';
import withToggle from 'HOCs/withToggle';
import Sentry from './Sentry';
import JsonViewer from './JsonViewer';
@ -54,34 +54,42 @@ export default class UserEvent extends React.PureComponent {
return !!this.props.userEvent.payload;
}
onClickDetails = (e) => {
e.stopPropagation();
this.props.switchOpen();
}
renderContent(modalTrigger) {
const { userEvent } = this.props;
//const message = this.getEventMessage();
return (
<div
data-scroll-item={ userEvent.isRed() }
onClick={ this.props.switchOpen } //
className={
cn(
stl.userEvent,
this.getLevelClassname(),
{ [ stl.modalTrigger ]: modalTrigger }
)
}
>
<div className={ stl.infoWrapper }>
<div
className={ stl.title }
>
<Icon { ...this.getIconProps() } />
{ userEvent.name }
</div>
{ /* message &&
<div className={ stl.message }>
{ message }
</div> */
}
</div>
// onClick={ this.props.switchOpen } //
onClick={ this.props.onJump } //
className={
cn(
"group",
stl.userEvent,
this.getLevelClassname(),
{ [ stl.modalTrigger ]: modalTrigger }
)
}
>
<div className={ stl.infoWrapper }>
<div className={ stl.title } >
<Icon { ...this.getIconProps() } />
{ userEvent.name }
</div>
{ /* message &&
<div className={ stl.message }>
{ message }
</div> */
}
<div className="invisible self-end ml-auto group-hover:visible">
<IconButton size="small" plain onClick={this.onClickDetails} label="DETAILS" />
</div>
</div>
</div>
);
}
@ -91,15 +99,15 @@ export default class UserEvent extends React.PureComponent {
if (this.ifNeedModal()) {
return (
<React.Fragment>
<SlideModal
//title="Add Custom Field"
size="middle"
isDisplayed={ this.props.open }
content={ this.props.open && this.renderPopupContent() }
onClose={ this.props.switchOpen }
/>
{ this.renderContent(true) }
</React.Fragment>
<SlideModal
//title="Add Custom Field"
size="middle"
isDisplayed={ this.props.open }
content={ this.props.open && this.renderPopupContent() }
onClose={ this.props.switchOpen }
/>
{ this.renderContent(true) }
</React.Fragment>
//<Modal
// trigger={ this.renderContent(true) }
// content={ this.renderPopupContent() }

View file

@ -1,13 +1,13 @@
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { withSiteId } from 'App/routes';
import { setSiteId } from 'Duck/user';
import { setSiteId } from 'Duck/site';
export default BaseComponent =>
@withRouter
@connect((state, props) => ({
urlSiteId: props.match.params.siteId,
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}), {
setSiteId,
})

View file

@ -1,11 +1,11 @@
import { connect } from 'react-redux';
import { withSiteId } from 'App/routes';
import { setSiteId } from 'Duck/user';
import { setSiteId } from 'Duck/site';
export default BaseComponent =>
@connect((state, props) => ({
urlSiteId: props.match.params.siteId,
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}), {
setSiteId,
})

View file

@ -28,7 +28,7 @@ const SESSIONS_ROUTE = sessionsRoute();
// )
@connect(state => ({
timezone: state.getIn(['sessions', 'timezone']),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
}), { toggleFavorite, setSessionPath })
@withRouter
export default class SessionItem extends React.PureComponent {

View file

@ -5,7 +5,7 @@ const SiteDropdown = ({ contextName="", sites, onChange, value }) => {
const options = sites.map(site => ({ value: site.id, text: site.host })).toJS();
return (
<Select
name={ `${ contextName }_site` }
name={ `${ contextName }_site` }
placeholder="Select Site"
options={ options }
value={ value }
@ -17,5 +17,5 @@ const SiteDropdown = ({ contextName="", sites, onChange, value }) => {
SiteDropdown.displayName = "SiteDropdown";
export default connect(state => ({
sites: state.getIn([ 'user', 'client', 'sites' ]),
sites: state.getIn([ 'site', 'list' ]),
}))(SiteDropdown);

View file

@ -15,5 +15,5 @@ const OpenReplayLink = ({ siteId, to, className="", dispatch, ...other }) => (
OpenReplayLink.displayName = 'OpenReplayLink';
export default connect((state, props) => ({
siteId: props.siteId || state.getIn([ 'user', 'siteId' ])
siteId: props.siteId || state.getIn([ 'site', 'siteId' ])
}))(OpenReplayLink);

View file

@ -145,7 +145,7 @@ export default connect(state => ({
loading: state.getIn(['funnels', 'fetchListRequest', 'loading']),
activeFlow: state.getIn([ 'filters', 'activeFlow' ]),
activeTab: state.getIn([ 'sessions', 'activeTab' ]),
siteId: state.getIn([ 'user', 'siteId' ]),
siteId: state.getIn([ 'site', 'siteId' ]),
events: state.getIn([ 'filters', 'appliedFilter', 'events' ]),
filters: state.getIn([ 'search', 'instance', 'filters' ]),
}), {

View file

@ -19,6 +19,9 @@ import {
import { createRequestReducer } from './funcTools/request';
import { Map, List, fromJS } from "immutable";
const SITE_ID_STORAGE_KEY = "__$user-siteId$__";
const storedSiteId = localStorage.getItem(SITE_ID_STORAGE_KEY);
const name = 'project';
const idKey = 'id';
const itemInListUpdater = createItemInListUpdater(idKey)
@ -26,13 +29,17 @@ const itemInListUpdater = createItemInListUpdater(idKey)
const EDIT_GDPR = 'sites/EDIT_GDPR';
const SAVE_GDPR = 'sites/SAVE_GDPR';
const FETCH_GDPR = 'sites/FETCH_GDPR';
const FETCH_LIST = 'sites/FETCH_LIST';
const SET_SITE_ID = 'sites/SET_SITE_ID';
const FETCH_GDPR_SUCCESS = success(FETCH_GDPR);
const SAVE_GDPR_SUCCESS = success(SAVE_GDPR);
const FETCH_LIST_SUCCESS = success(FETCH_LIST);
const initialState = Map({
list: List(),
instance: fromJS(),
remainingSites: undefined,
siteId: null,
});
const reducer = (state = initialState, action = {}) => {
@ -44,10 +51,18 @@ const reducer = (state = initialState, action = {}) => {
case SAVE_GDPR_SUCCESS:
const gdpr = GDPR(action.data);
return state.setIn([ 'instance', 'gdpr' ], gdpr);
// return state.update('list', itemInListUpdater({
// [ idKey ] : state.getIn([ 'instance', idKey ]),
// gdpr,
// })).setIn([ 'instance', 'gdpr' ], gdpr);
case FETCH_LIST_SUCCESS:
let siteId = state.get("siteId");
if (!siteId) {
siteId = !!action.data.find(s => s.projectId === storedSiteId)
? storedSiteId
: action.data[0].projectId;
}
console.log('siteId asd', siteId)
return state.set('list', List(action.data.map(Site))).set('siteId', siteId);
case SET_SITE_ID:
localStorage.setItem(SITE_ID_STORAGE_KEY, action.siteId)
return state.set('siteId', action.siteId);
}
return state;
};
@ -73,13 +88,27 @@ export function saveGDPR(siteId, gdpr) {
};
}
export const fetchList = createFetchList(name);
export function fetchList() {
return {
types: array(FETCH_LIST),
call: client => client.get('/projects'),
};
}
// export const fetchList = createFetchList(name);
export const init = createInit(name);
export const edit = createEdit(name);
export const save = createSave(name);
export const update = createUpdate(name);
export const remove = createRemove(name);
export function setSiteId(siteId) {
return {
type: SET_SITE_ID,
siteId,
};
}
export default mergeReducers(
reducer,
createCRUDReducer(name, Site, idKey),

View file

@ -18,12 +18,8 @@ export const UPDATE_PASSWORD = new RequestTypes('user/UPDATE_PASSWORD');
const PUT_CLIENT = new RequestTypes('user/PUT_CLIENT');
const PUSH_NEW_SITE = 'user/PUSH_NEW_SITE';
const SET_SITE_ID = 'user/SET_SITE_ID';
const SET_ONBOARDING = 'user/SET_ONBOARDING';
const SITE_ID_STORAGE_KEY = "__$user-siteId$__";
const storedSiteId = localStorage.getItem(SITE_ID_STORAGE_KEY);
const initialState = Map({
client: Client(),
account: Account(),
@ -32,20 +28,13 @@ const initialState = Map({
passwordErrors: List(),
tenants: [],
authDetails: {},
onboarding: false
onboarding: false,
sites: List()
});
const setClient = (state, data) => {
const client = Client(data);
let siteId = state.get("siteId");
if (!siteId) {
siteId = !!client.sites.find(s => s.id === storedSiteId)
? storedSiteId
: client.getIn([ 'sites', 0, 'id' ]);
}
return state
.set('client', client)
.set('siteId', siteId);
return state.set('client', client)
}
const reducer = (state = initialState, action = {}) => {
@ -80,11 +69,8 @@ const reducer = (state = initialState, action = {}) => {
return state.mergeIn([ 'client' ], action.params);
case FETCH_CLIENT.SUCCESS:
return setClient(state, action.data);
case SET_SITE_ID:
localStorage.setItem(SITE_ID_STORAGE_KEY, action.siteId)
return state.set('siteId', action.siteId);
case PUSH_NEW_SITE:
return state.updateIn([ 'client', 'sites' ], list =>
return state.updateIn([ 'site', 'list' ], list =>
list.push(action.newSite));
case SET_ONBOARDING:
return state.set('onboarding', action.state)
@ -185,13 +171,6 @@ export function updateAppearance(appearance) {
};
}
export function setSiteId(siteId) {
return {
type: SET_SITE_ID,
siteId,
};
}
export function pushNewSite(newSite) {
return {
type: PUSH_NEW_SITE,

View file

@ -5,6 +5,7 @@ import { dashboardService, metricService } from "App/services";
import { toast } from 'react-toastify';
import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period';
import { getChartFormatter } from 'Types/dashboard/helper';
import Filter from "./types/filter";
export interface IDashboardSotre {
dashboards: IDashboard[]
@ -14,6 +15,7 @@ export interface IDashboardSotre {
startTimestamp: number
endTimestamp: number
period: Period
drillDownFilter: Filter
siteId: any
currentWidget: Widget
@ -73,6 +75,7 @@ export default class DashboardStore implements IDashboardSotre {
widgetCategories: any[] = []
widgets: Widget[] = []
period: Period = Period({ rangeName: LAST_7_DAYS })
drillDownFilter: Filter = new Filter()
startTimestamp: number = 0
endTimestamp: number = 0
@ -114,6 +117,10 @@ export default class DashboardStore implements IDashboardSotre {
fetchMetricChartData: action
})
const drillDownPeriod = Period({ rangeName: LAST_7_DAYS }).toTimestamps();
this.drillDownFilter.updateKey('startTimestamp', drillDownPeriod.startTimestamp)
this.drillDownFilter.updateKey('endTimestamp', drillDownPeriod.endTimestamp)
}
toggleAllSelectedWidgets(isSelected: boolean) {
@ -434,7 +441,9 @@ export default class DashboardStore implements IDashboardSotre {
// metric.setData(_data)
// resolve(_data);
const _data = {}
const _data = {
...data,
}
if (data.hasOwnProperty('chart')) {
_data['chart'] = getChartFormatter(this.period)(data.chart)
_data['namesMap'] = data.chart
@ -461,10 +470,9 @@ export default class DashboardStore implements IDashboardSotre {
}
metric.setData(_data)
resolve({ ...data, ..._data });
resolve(_data);
}
}).catch((err) => {
console.log('err', err)
reject(err)
})
})

View file

@ -1,16 +1,14 @@
import { filter } from "App/components/BugFinder/ManageFilters/savedFilterList.css"
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
import { FilterKey, FilterType } from 'Types/filter/filterType'
import { filtersMap } from 'Types/filter/newFilter'
import FilterItem from "./filterItem"
// console.log('filtersMap', filtersMap)
export default class Filter {
public static get ID_KEY():string { return "filterId" }
name: string = ''
filters: FilterItem[] = []
eventsOrder: string = 'then'
startTimestamp: number = 0
endTimestamp: number = 0
constructor() {
makeAutoObservable(this, {
@ -53,6 +51,17 @@ export default class Filter {
return this
}
toJsonDrilldown() {
const json = {
name: this.name,
filters: this.filters.map(i => i.toJson()),
eventsOrder: this.eventsOrder,
startTimestamp: this.startTimestamp,
endTimestamp: this.endTimestamp,
}
return json
}
toJson() {
const json = {
name: this.name,

View file

@ -1,7 +1,7 @@
import { List } from 'immutable';
// import { List } from 'immutable';
import Record from 'Types/Record';
import Site from 'Types/site';
import { DateTime } from 'luxon';
// import Site from 'Types/site';
// import { DateTime } from 'luxon';
import LoggerOptions from './loggerOptions';
export default Record({
@ -10,7 +10,7 @@ export default Record({
tenantId: undefined,
tenantKey: '',
name: undefined,
sites: List(),
// sites: List(),
optOut: true,
edition: '',
}, {
@ -21,6 +21,6 @@ export default Record({
}) => ({
...rest,
loggerOptions: LoggerOptions(loggerOptions),
sites: List(projects).map(Site),
// sites: List(projects).map(Site),
}),
});