diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx deleted file mode 100644 index 3a102a9c4..000000000 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorDetails.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from 'react' -import ErrorFrame from './ErrorFrame' -import { IconButton, Icon } from 'UI'; - -const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; - -interface Props { - error: any, - errorStack: any, -} -function ErrorDetails({ className, name = "Error", message, errorStack, sourcemapUploaded }: any) { - const [showRaw, setShowRaw] = useState(false) - const firstFunc = errorStack.first() && errorStack.first().function - - const openDocs = () => { - window.open(docLink, '_blank'); - } - - return ( -
- { !sourcemapUploaded && ( -
- -
Source maps must be uploaded to OpenReplay to be able to see stack traces. Learn more.
-
- ) } -
-

- Stacktrace -

-
- setShowRaw(false) } - label="FULL" - plain={!showRaw} - primaryText={!showRaw} - /> - setShowRaw(true) } - plain={showRaw} - label="RAW" - /> -
-
-
-
{ name }
-
{message}
-
- { showRaw && -
{name} : {firstFunc ? firstFunc : '?' }
- } - { errorStack.map((frame: any, i: any) => ( -
- -
- )) - } -
- ) -} - -ErrorDetails.displayName = "ErrorDetails"; -export default ErrorDetails; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/ErrorFrame.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/ErrorFrame.tsx deleted file mode 100644 index ece2b816c..000000000 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/ErrorFrame.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react' -import { Icon } from 'UI'; -import cn from 'classnames'; -import stl from './errorFrame.module.css'; - -function ErrorFrame({ frame = {}, showRaw, isFirst }) { - const [open, setOpen] = useState(isFirst) - const hasContext = frame.context && frame.context.length > 0; - return ( -
- { showRaw ? -
at { frame.function ? frame.function : '?' } ({`${frame.filename}:${frame.lineNo}:${frame.colNo}`})
- : -
-
setOpen(!open)}> -
- { frame.absPath } - { frame.function && - <> - {' in '} - {frame.function} - - } - {' at line '} - - {frame.lineNo}:{frame.colNo} - -
- { hasContext && -
- -
- } -
- { open && hasContext && -
    - { frame.context.map(i => ( -
  1. - { i[1].replace(/ /g, "\u00a0") } -
  2. - ))} -
- } -
- } -
- ) -} - -export default ErrorFrame; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/errorFrame.module.css b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/errorFrame.module.css deleted file mode 100644 index 5e7f69e75..000000000 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/errorFrame.module.css +++ /dev/null @@ -1,25 +0,0 @@ -.rawLine { - margin-left: 30px; - font-family: 'Menlo', 'monaco', 'consolas', monospace; - font-size: 13px; -} -.formatted { - border: solid thin #EEE; - border-radius: 3px; -} -.header { - background-color: $gray-lightest; - padding: 8px; - border-bottom: solid thin #EEE; -} -.content { - font-family: 'Menlo', 'monaco', 'consolas', monospace; - list-style-position: inside; - list-style-type: decimal-leading-zero; -} - -.errorLine { - background-color: $teal; - color: white !important; - font-weight: bold; -} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/index.js b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/index.js deleted file mode 100644 index 70caa16f6..000000000 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/ErrorFrame/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorFrame'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/index.js b/frontend/app/components/Dashboard/components/Errors/ErrorDetails/index.js deleted file mode 100644 index 987da1320..000000000 --- a/frontend/app/components/Dashboard/components/Errors/ErrorDetails/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ErrorDetails'; diff --git a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx index b208358b8..68b30b16e 100644 --- a/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx +++ b/frontend/app/components/Dashboard/components/Errors/ErrorListItem/ErrorListItem.tsx @@ -17,10 +17,7 @@ function ErrorListItem(props: Props) { const { error, className = '' } = props; // const { showModal } = useModal(); - // const onClick = () => { - // alert('test') - // showModal(, { right: true }); - // } + return (
- { ); } - + return null; }; diff --git a/frontend/app/components/Errors/Error/ErrorInfo.js b/frontend/app/components/Errors/Error/ErrorInfo.js index 1785bb4cb..5c70faff6 100644 --- a/frontend/app/components/Errors/Error/ErrorInfo.js +++ b/frontend/app/components/Errors/Error/ErrorInfo.js @@ -11,14 +11,14 @@ import SideSection from './SideSection'; function ErrorInfo(props) { const { errorStore } = useStore(); - + const instance = errorStore.instance; const ensureInstance = () => { if (errorStore.isLoading) return; - errorStore.fetch(props.errorId); - errorStore.fetchTrace(props.errorId); + errorStore.fetchError(props.errorId); + errorStore.fetchErrorTrace(props.errorId); }; - useEffect(() => { + React.useEffect(() => { ensureInstance(); }, [props.errorId]); @@ -36,7 +36,7 @@ function ErrorInfo(props) { show={!loading && errorIdInStore == null} >
- + diff --git a/frontend/app/components/Errors/Error/MainSection.js b/frontend/app/components/Errors/Error/MainSection.js index 86d3d26d7..621c9f2b3 100644 --- a/frontend/app/components/Errors/Error/MainSection.js +++ b/frontend/app/components/Errors/Error/MainSection.js @@ -28,7 +28,7 @@ function MainSection(props) { const findSessions = () => { addFilterByKeyAndValue(FilterKey.ERROR, error.message); - this.props.history.push(sessionsRoute()); + props.history.push(sessionsRoute()); }; return (
count); - const sum = counts.reduce((a,b)=>parseInt(a)+parseInt(b),0); + const sum = counts.reduce((a, b) => parseInt(a) + parseInt(b), 0); if (sum === 0) { - return []; + return []; } - const otherPrcs = counts - .map(c => c/sum * 100) - .filter(hidePredicate); - const otherPrcsSum = otherPrcs.reduce((a,b)=>a+b,0); - const showLength = partitions.length - otherPrcs.length; - const show = partitions - .sort((a, b) => b.count - a.count) - .slice(0, showLength) - .map(p => ({ - label: mapCountry - ? (countries[p.name] || "Unknown") - : p.name, - prc: p.count/sum * 100, - })) + const otherPrcs = counts.map((c) => (c / sum) * 100).filter(hidePredicate); + const otherPrcsSum = otherPrcs.reduce((a, b) => a + b, 0); + const showLength = partitions.length - otherPrcs.length; + const show = partitions + .sort((a, b) => b.count - a.count) + .slice(0, showLength) + .map((p) => ({ + label: mapCountry ? countries[p.name] || 'Unknown' : p.name, + prc: (p.count / sum) * 100, + })); if (otherPrcsSum > 0) { show.push({ - label: "Other", + label: 'Other', prc: otherPrcsSum, other: true, - }) + }); } return show; } function tagsWrapper(tags = []) { - return tags.map(({ name, partitions }) => ({ - name, - partitions: partitionsWrapper(partitions, name === "country") - })) + return tags.map(({ name, partitions }) => ({ + name, + partitions: partitionsWrapper(partitions, name === 'country'), + })); } function dataWrapper(data = {}) { - return { - chart24: data.chart24 || [], - chart30: data.chart30 || [], - tags: tagsWrapper(data.tags), - }; + return { + chart24: data.chart24 || [], + chart30: data.chart30 || [], + tags: tagsWrapper(data.tags), + }; } -@connect(state => ({ - error: state.getIn([ "errors", "instance" ]) -})) -@withRequest({ - initialData: props => dataWrapper(props.error), - endpoint: props => `/errors/${ props.error.errorId }/stats`, - dataWrapper, -}) -export default class SideSection extends React.PureComponent { - onDateChange = ({ startDate, endDate }) => { - this.props.request({ startDate, endDate }); - } +function SideSection(props) { + const [data, setData] = React.useState({ + chart24: [], + chart30: [], + tags: [], + }); + const [loading, setLoading] = React.useState(false); + const { className } = props; + const { errorStore } = useStore(); + const error = errorStore.instance; - render() { - const { - className, - error, - data, - loading, - } = this.props; - return ( -
-

Overview

- -
- -
- - - { data.tags.length > 0 &&

Summary

} - - { data.tags.map(({ name, partitions }) => - - )} - -
- ); - } + const grabData = async () => { + setLoading(true); + errorService.fetchErrorStats(error.errorId) + .then(data => { + setData(dataWrapper(data)) + }) + .finally(() => setLoading(false)); + } + + React.useEffect(() => { + setData(dataWrapper(error)) + }, [error.errorId]) + + return ( +
+

Overview

+ +
+ +
+ + + {data.tags.length > 0 &&

Summary

} + + {data.tags.map(({ name, partitions }) => ( + + ))} + +
+ ); } + +export default observer(SideSection); diff --git a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx index 2aca274ea..02ce0af1b 100644 --- a/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx +++ b/frontend/app/components/ui/ErrorDetails/ErrorDetails.tsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from 'react'; import ErrorFrame from '../ErrorFrame/ErrorFrame'; -import { fetchErrorStackList } from 'Duck/sessions'; import { Button, Icon } from 'UI'; import { connect } from 'react-redux'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; const docLink = 'https://docs.openreplay.com/installation/upload-sourcemaps'; @@ -15,21 +16,16 @@ interface Props { error: any; } function ErrorDetails(props: Props) { - const { error, sessionId, message = '', errorStack = [], sourcemapUploaded = false } = props; + const { errorStore } = useStore(); + const errorStack = errorStore.instanceTrace; + const { error, sessionId, message = '', sourcemapUploaded = false } = props; const [showRaw, setShowRaw] = useState(false); - const firstFunc = errorStack.first() && errorStack.first().function; - + const firstFunc = errorStack[0] && errorStack[0].function; const openDocs = () => { window.open(docLink, '_blank'); }; - useEffect(() => { - if (sessionId) { - props.fetchErrorStackList(sessionId, error.errorId); - } - }, []); - return (
{!sourcemapUploaded && ( @@ -78,9 +74,6 @@ function ErrorDetails(props: Props) { ErrorDetails.displayName = 'ErrorDetails'; export default connect( (state: any) => ({ - // errorStack: state.getIn(['sessions', 'errorStack']), - errorStack: state.getIn(['errors', 'instanceTrace']), sessionId: state.getIn(['sessions', 'current']).sessionId, - }), - { fetchErrorStackList } -)(ErrorDetails); + }) +)(observer(ErrorDetails)); diff --git a/frontend/app/mstore/errorStore.ts b/frontend/app/mstore/errorStore.ts index 909dfcff6..6b0ce0c9e 100644 --- a/frontend/app/mstore/errorStore.ts +++ b/frontend/app/mstore/errorStore.ts @@ -1,13 +1,13 @@ import { makeAutoObservable } from 'mobx'; import apiClient from 'App/api_client'; -import { errorService } from 'App/services'; +import { errorService, sessionService } from 'App/services'; import { ErrorInfo } from './types/error'; export default class ErrorStore { instance: ErrorInfo | null = null; - instanceTrace: Record = []; + instanceTrace: Record[] = []; stats: Record = {}; sourcemapUploaded = false; isLoading = false; @@ -65,8 +65,8 @@ export default class ErrorStore { try { const response = await errorService.fetchErrorTrace(id); - this.setInstanceTrace(response.data.trace); - this.setSourcemapUploaded(response.data.sourcemapUploaded); + this.setInstanceTrace(response.trace); + this.setSourcemapUploaded(response.sourcemapUploaded); } catch (error) { this.setErrorState(actionKey, error); } finally { diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 6119ddff5..950e79249 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -2,7 +2,7 @@ import { makeAutoObservable } from 'mobx'; import Widget from './types/widget'; import { metricService, errorService } from 'App/services'; import { toast } from 'react-toastify'; -import Error from './types/error'; +import { ErrorInfo } from './types/error'; import { TIMESERIES, TABLE, @@ -318,7 +318,7 @@ export default class MetricStore { errorService .one(errorId) .then((error: any) => { - resolve(new Error().fromJSON(error)); + resolve(new ErrorInfo(error)); }) .catch((error: any) => { toast.error('Failed to fetch error details.'); diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 8495f1aed..99f1ad92d 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -9,7 +9,7 @@ import Period, {LAST_24_HOURS} from 'Types/app/period'; import Funnel from '../types/funnel'; import {metricService} from 'App/services'; import { FUNNEL, HEATMAP, INSIGHTS, TABLE, USER_PATH, WEB_VITALS } from "App/constants/card"; -import Error from '../types/error'; +import { ErrorInfo } from '../types/error'; import {getChartFormatter} from 'Types/dashboard/helper'; import FilterItem from './filterItem'; import {filtersMap} from 'Types/filter/newFilter'; @@ -299,7 +299,7 @@ export default class Widget { } if (this.metricOf === FilterKey.ERRORS) { - _data['errors'] = data.errors.map((s: any) => new Error().fromJSON(s)); + _data['errors'] = data.errors.map((s: any) => new ErrorInfo(s)); } else if (this.metricType === INSIGHTS) { _data['issues'] = data .filter((i: any) => i.change > 0 || i.change < 0) diff --git a/frontend/app/services/ErrorService.ts b/frontend/app/services/ErrorService.ts index dc8b3336e..70d51ccc3 100644 --- a/frontend/app/services/ErrorService.ts +++ b/frontend/app/services/ErrorService.ts @@ -1,4 +1,5 @@ import BaseService from './BaseService'; +import { IErrorStack } from 'Types/session/errorStack'; export default class ErrorService extends BaseService { fetchError = async (id: string) => { @@ -13,10 +14,11 @@ export default class ErrorService extends BaseService { return await r.json(); }; - fetchErrorTrace = async (id: string) => { + fetchErrorTrace = async (id: string): Promise<{ trace: IErrorStack[], sourcemapUploaded: boolean }> => { const r = await this.client.get(`/errors/${id}/sourcemaps`); + const { data } = await r.json() - return await r.json(); + return data; }; fetchNewErrorsCount = async (params: any) => { @@ -24,4 +26,10 @@ export default class ErrorService extends BaseService { return await r.json(); }; + + fetchErrorStats = async (errorId: string) => { + const r = await this.client.get(`/errors/${errorId}/stats`); + + return await r.json(); + } }