diff --git a/frontend/app/components/Session_/Network/Network.DEPRECATED.js b/frontend/app/components/Session_/Network/Network.DEPRECATED.js deleted file mode 100644 index b79307431..000000000 --- a/frontend/app/components/Session_/Network/Network.DEPRECATED.js +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connectPlayer, } from 'Player'; -import { Tooltip, TextEllipsis } from 'UI'; -import { getRE } from 'App/utils'; -import { TYPES } from 'Types/session/resource'; -import stl from './network.module.css'; -import NetworkContent from './NetworkContent'; -import { connect } from 'react-redux'; -import { setTimelinePointer } from 'Duck/sessions'; - -const ALL = 'ALL'; -const XHR = 'xhr'; -const JS = 'js'; -const CSS = 'css'; -const IMG = 'img'; -const MEDIA = 'media'; -const OTHER = 'other'; - -const TAB_TO_TYPE_MAP = { - [XHR]: TYPES.XHR, - [JS]: TYPES.JS, - [CSS]: TYPES.CSS, - [IMG]: TYPES.IMG, - [MEDIA]: TYPES.MEDIA, - [OTHER]: TYPES.OTHER, -}; - -export function renderName(r) { - return ( -
- {r.url}
} - > - {r.name} - - - ); -} - -export function renderDuration(r) { - if (!r.success) return 'x'; - - const text = `${Math.round(r.duration)}ms`; - if (!r.isRed && !r.isYellow) return text; - - let tooltipText; - let className = 'w-full h-full flex items-center '; - if (r.isYellow) { - tooltipText = 'Slower than average'; - className += 'warn color-orange'; - } else { - tooltipText = 'Much slower than average'; - className += 'error color-red'; - } - - return ( - -
{text}
-
- ); -} - -@connectPlayer((state) => ({ - location: state.location, - resources: state.resourceList, - domContentLoadedTime: state.domContentLoadedTime, - loadTime: state.loadTime, - // time: state.time, - playing: state.playing, - domBuildingTime: state.domBuildingTime, - fetchPresented: state.fetchList.length > 0, - listNow: state.resourceListNow, -})) -@connect( - (state) => ({ - timelinePointer: state.getIn(['sessions', 'timelinePointer']), - }), - { setTimelinePointer } -) -export default class Network extends React.PureComponent { - state = { - filter: '', - filteredList: this.props.resources, - activeTab: ALL, - currentIndex: 0, - }; - - onRowClick = (e, index) => { - // no action for direct click on network requests (so far), there is a jump button, and we don't have more information for than is already displayed in the table - }; - - onTabClick = (activeTab) => this.setState({ activeTab }); - - onFilterChange = (e, { value }) => { - const { resources } = this.props; - const filterRE = getRE(value, 'i'); - const filtered = resources.filter( - ({ type, name }) => - filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) - ); - - this.setState({ filter: value, filteredList: value ? filtered : resources, currentIndex: 0 }); - }; - - static getDerivedStateFromProps(nextProps, prevState) { - const { filteredList } = prevState; - if (nextProps.timelinePointer) { - const activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time); - return { - currentIndex: activeItem ? filteredList.indexOf(activeItem) : filteredList.length - 1, - }; - } - } - - render() { - const { location, domContentLoadedTime, loadTime, domBuildingTime, fetchPresented, listNow } = - this.props; - const { filteredList } = this.state; - const resourcesSize = filteredList.reduce( - (sum, { decodedBodySize }) => sum + (decodedBodySize || 0), - 0 - ); - const transferredSize = filteredList.reduce( - (sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), - 0 - ); - - return ( - - - - ); - } -} diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 5718fb55e..f55f5e407 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -2,7 +2,7 @@ import React from 'react'; import cn from 'classnames'; import { QuestionMarkHint, Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; import { getRE } from 'App/utils'; -import { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import { formatBytes } from 'App/utils'; import { formatMs } from 'App/date'; @@ -21,12 +21,12 @@ const MEDIA = 'media'; const OTHER = 'other'; const TAB_TO_TYPE_MAP = { - [XHR]: TYPES.XHR, - [JS]: TYPES.JS, - [CSS]: TYPES.CSS, - [IMG]: TYPES.IMG, - [MEDIA]: TYPES.MEDIA, - [OTHER]: TYPES.OTHER, + [XHR]: ResourceType.XHR, + [JS]: ResourceType.SCRIPT, + [CSS]: ResourceType.CSS, + [IMG]: ResourceType.IMG, + [MEDIA]: ResourceType.MEDIA, + [OTHER]: ResourceType.OTHER, }; const TABS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ text: tab, diff --git a/frontend/app/components/Session_/Network/index.js b/frontend/app/components/Session_/Network/index.js deleted file mode 100644 index 446e76ea6..000000000 --- a/frontend/app/components/Session_/Network/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Network'; -export * from './Network'; diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 9339b1e8b..b7207ddef 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite'; import { Duration } from 'luxon'; import { Tooltip, Tabs, Input, NoContent, Icon, Toggler } from 'UI'; -import { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import { formatBytes } from 'App/utils'; import { formatMs } from 'App/date'; import { useModal } from 'App/components/Modal'; @@ -28,13 +28,13 @@ const MEDIA = 'media'; const OTHER = 'other'; const TYPE_TO_TAB = { - [TYPES.XHR]: XHR, - [TYPES.FETCH]: XHR, - [TYPES.JS]: JS, - [TYPES.CSS]: CSS, - [TYPES.IMG]: IMG, - [TYPES.MEDIA]: MEDIA, - [TYPES.OTHER]: OTHER, + [ResourceType.XHR]: XHR, + [ResourceType.FETCH]: XHR, + [ResourceType.SCRIPT]: JS, + [ResourceType.CSS]: CSS, + [ResourceType.IMG]: IMG, + [ResourceType.MEDIA]: MEDIA, + [ResourceType.OTHER]: OTHER, } const TAP_KEYS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER] as const; @@ -154,7 +154,7 @@ function NetworkPanel({ startedAt }: { startedAt: number }) { const activeIndex = devTools[INDEX_KEY].index; const list = useMemo(() => - // TODO: better merge (with body size info) + // TODO: better merge (with body size info) - do it in player resourceList.filter(res => !fetchList.some(ft => { // res.url !== ft.url doesn't work on relative URLs appearing within fetchList (to-fix in player) if (res.name !== ft.name) { return false } diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx index 49d6536bc..09a08ae16 100644 --- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx +++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import FetchBasicDetails from './components/FetchBasicDetails'; import { Button } from 'UI'; -import { TYPES } from 'Types/session/resource'; +import { ResourceType } from 'Player'; import FetchTabs from './components/FetchTabs/FetchTabs'; import { useStore } from 'App/mstore'; import { DateTime } from 'luxon'; @@ -17,7 +17,7 @@ function FetchDetailsModal(props: Props) { const [resource, setResource] = useState(props.resource); const [first, setFirst] = useState(false); const [last, setLast] = useState(false); - const isXHR = resource.type === TYPES.XHR || resource.type === TYPES.FETCH + const isXHR = resource.type === ResourceType.XHR || resource.type === ResourceType.FETCH const { sessionStore: { devTools }, settingsStore: { sessionSettings: { timezone }}, diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 68ef0cbf8..72c07c445 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -2,9 +2,9 @@ import { Decoder } from "syncod"; import logger from 'App/logger'; -import Resource, { TYPES as RES_TYPES } from 'Types/session/resource'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import { Log } from './types'; +import { Log } from './types/log'; +import { Resource, ResourceType, getResourceFromResourceTiming, getResourceFromNetworkRequest } from './types/resource' import { toast } from 'react-toastify'; @@ -395,19 +395,13 @@ export default class MessageManager { Log(msg) ) break; + case MType.ResourceTiming: + // TODO: merge `resource` and `fetch` lists into one here instead of UI + this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) + break; case MType.Fetch: case MType.NetworkRequest: - this.lists.lists.fetch.insert(new Resource({ - method: msg.method, - url: msg.url, - request: msg.request, - response: msg.response, - status: msg.status, - duration: msg.duration, - type: msg.type === "xhr" ? RES_TYPES.XHR : RES_TYPES.FETCH, - time: Math.max(msg.timestamp - this.sessionStart, 0), // !!! doesn't look good. TODO: find solution to show negative timings - index, - }) as Timed) + this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) break; case MType.Redux: decoded = this.decodeStateMessage(msg, ["state", "action"]); diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index d94d10beb..d1a56f9fd 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -1,4 +1,4 @@ -import { Log, LogLevel } from './types' +import { Log, LogLevel } from './types/log' import type { Store } from 'App/player' import Player from '../player/Player' @@ -30,7 +30,6 @@ export default class WebPlayer extends Player { let initialLists = live ? {} : { event: session.events || [], stack: session.stackEvents || [], - resource: session.resources || [], // MBTODO: put ResourceTiming in file exceptions: session.errors?.map(({ name, ...rest }: any) => Log({ level: LogLevel.ERROR, diff --git a/frontend/app/player/web/types.ts b/frontend/app/player/web/types.ts deleted file mode 100644 index 485921ed0..000000000 --- a/frontend/app/player/web/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -export enum LogLevel { - INFO = 'info', - LOG = 'log', - //ASSERT = 'assert', //? - WARN = 'warn', - ERROR = 'error', - EXCEPTION = 'exception', -} - -export interface ILog { - level: LogLevel - value: string - time: number - index?: number - errorId?: string -} - -export const Log = (log: ILog) => ({ - isRed: log.level === LogLevel.EXCEPTION || log.level === LogLevel.ERROR, - isYellow: log.level === LogLevel.WARN, - ...log -}) - - - - -// func getResourceType(initiator string, URL string) string { -// switch initiator { -// case "xmlhttprequest", "fetch": -// return "fetch" -// case "img": -// return "img" -// default: -// switch getURLExtention(URL) { -// case "css": -// return "stylesheet" -// case "js": -// return "script" -// case "png", "gif", "jpg", "jpeg", "svg": -// return "img" -// case "mp4", "mkv", "ogg", "webm", "avi", "mp3": -// return "media" -// default: -// return "other" -// } -// } -// } \ No newline at end of file diff --git a/frontend/app/player/web/types/index.ts b/frontend/app/player/web/types/index.ts new file mode 100644 index 000000000..2f5f8e464 --- /dev/null +++ b/frontend/app/player/web/types/index.ts @@ -0,0 +1,2 @@ +export * from './log' +export * from './resource' \ No newline at end of file diff --git a/frontend/app/player/web/types/log.ts b/frontend/app/player/web/types/log.ts new file mode 100644 index 000000000..22a20d33c --- /dev/null +++ b/frontend/app/player/web/types/log.ts @@ -0,0 +1,23 @@ +export const enum LogLevel { + INFO = 'info', + LOG = 'log', + //ASSERT = 'assert', //? + WARN = 'warn', + ERROR = 'error', + EXCEPTION = 'exception', +} + +export interface ILog { + level: LogLevel + value: string + time: number + index?: number + errorId?: string +} + +export const Log = (log: ILog) => ({ + isRed: log.level === LogLevel.EXCEPTION || log.level === LogLevel.ERROR, + isYellow: log.level === LogLevel.WARN, + ...log +}) + diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts new file mode 100644 index 000000000..032790e48 --- /dev/null +++ b/frontend/app/player/web/types/resource.ts @@ -0,0 +1,114 @@ +import type { ResourceTiming, NetworkRequest, Fetch } from '../messages' + +export const enum ResourceType { + XHR = 'xhr', + FETCH = 'fetch', + SCRIPT = 'script', + CSS = 'css', + IMG = 'img', + MEDIA = 'media', + OTHER = 'other', +} + +function getURLExtention(url: string): string { + const pts = url.split(".") + return pts[pts.length-1] || "" +} + +// maybe move this thing to the tracker +function getResourceType(initiator: string, url: string): ResourceType { + switch (initiator) { + case "xmlhttprequest": + case "fetch": + return ResourceType.FETCH + case "img": + return ResourceType.IMG + default: + switch (getURLExtention(url)) { + case "css": + return ResourceType.CSS + case "js": + return ResourceType.SCRIPT + case "png": + case "gif": + case "jpg": + case "jpeg": + case "svg": + return ResourceType.IMG + case "mp4": + case "mkv": + case "ogg": + case "webm": + case "avi": + case "mp3": + return ResourceType.MEDIA + default: + return ResourceType.OTHER + } + } +} + +function getResourceName(url: string) { + return url + .split('/') + .filter((s) => s !== '') + .pop(); +} + + +const YELLOW_BOUND = 10; +const RED_BOUND = 80; + + +interface IResource { + //index: number, + time: number, + type: ResourceType, + url: string, + status: string, + method: string, + duration: number, + success: boolean, + ttfb?: number, + request?: string, + response?: string, + headerSize?: number, + encodedBodySize?: number, + decodedBodySize?: number, + responseBodySize?: number, +} + + +export const Resource = (resource: IResource) => ({ + ...resource, + name: getResourceName(resource.url), + isRed: !resource.success, //|| resource.score >= RED_BOUND, + isYellow: false, // resource.score < RED_BOUND && resource.score >= YELLOW_BOUND, +}) + + +export function getResourceFromResourceTiming(msg: ResourceTiming, sessStart: number) { + const success = msg.duration > 0 // might be duration=0 when cached + const type = getResourceType(msg.initiator, msg.url) + return Resource({ + ...msg, + type, + method: type === ResourceType.FETCH ? ".." : "GET", // should be GET for all non-XHR/Fetch resources, right? + success, + status: success ? '2xx-3xx' : '4xx-5xx', + time: Math.max(0, msg.timestamp - sessStart) + }) +} + +export function getResourceFromNetworkRequest(msg: NetworkRequest | Fetch, sessStart: number) { + return Resource({ + ...msg, + // @ts-ignore + type: msg?.type === "xhr" ? ResourceType.XHR : ResourceType.FETCH, + success: msg.status < 400, + status: String(msg.status), + time: Math.max(0, msg.timestamp - sessStart), + }) +} + + diff --git a/frontend/app/types/session/resource.ts b/frontend/app/types/session/resource.ts deleted file mode 100644 index 7e09e8f02..000000000 --- a/frontend/app/types/session/resource.ts +++ /dev/null @@ -1,103 +0,0 @@ -import Record from 'Types/Record'; -import { getResourceName } from 'App/utils'; - -const XHR = 'xhr' as const; -const FETCH = 'fetch' as const; -const JS = 'script' as const; -const CSS = 'css' as const; -const IMG = 'img' as const; -const MEDIA = 'media' as const; -const OTHER = 'other' as const; - -function getResourceStatus(status: number, success: boolean) { - if (status !== undefined) return String(status); - if (typeof success === 'boolean' || typeof success === 'number') { - return !!success - ? '2xx-3xx' - : '4xx-5xx'; - } - return '2xx-3xx'; -} - -export const TYPES = { - XHR, - FETCH, - JS, - CSS, - IMG, - MEDIA, - OTHER, - "stylesheet": CSS, -} - -const YELLOW_BOUND = 10; -const RED_BOUND = 80; - -export function isRed(r: IResource) { - return !r.success || r.score >= RED_BOUND; -} - -interface IResource { - type: keyof typeof TYPES, - url: string, - name: string, - status: number, - duration: number, - index: number, - time: number, - ttfb: number, - timewidth: number, - success: boolean, - score: number, - method: string, - request: string, - response: string, - headerSize: number, - encodedBodySize: number, - decodedBodySize: number, - responseBodySize: number, - timings: Record - datetime: number - timestamp: number -} - -export default class Resource { - name = 'Resource' - type: IResource["type"] - status: string - success: IResource["success"] - time: IResource["time"] - ttfb: IResource["ttfb"] - url: IResource["url"] - duration: IResource["duration"] - index: IResource["index"] - timewidth: IResource["timewidth"] - score: IResource["score"] - method: IResource["method"] - request: IResource["request"] - response: IResource["response"] - headerSize: IResource["headerSize"] - encodedBodySize: IResource["encodedBodySize"] - decodedBodySize: IResource["decodedBodySize"] - responseBodySize: IResource["responseBodySize"] - timings: IResource["timings"] - - constructor({ status, success, time, datetime, timestamp, timings, ...resource }: IResource) { - - // adjusting for 201, 202 etc - const reqSuccess = 300 > status || success - Object.assign(this, { - ...resource, - name: getResourceName(resource.url), - status: getResourceStatus(status, success), - success: reqSuccess, - time: typeof time === 'number' ? time : datetime || timestamp, - ttfb: timings && timings.ttfb, - timewidth: timings && timings.timewidth, - timings, - isRed: !reqSuccess || resource.score >= RED_BOUND, - isYellow: resource.score < RED_BOUND && resource.score >= YELLOW_BOUND, - }) - } -} - diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 4dcef8d49..3b254ae4b 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -1,7 +1,6 @@ import { Duration } from 'luxon'; import SessionEvent, { TYPES, EventData, InjectedEvent } from './event'; import StackEvent from './stackEvent'; -import Resource from './resource'; import SessionError, { IError } from './error'; import Issue, { IIssue } from './issue'; import { Note } from 'App/services/NotesService' @@ -31,8 +30,6 @@ export interface ISession { duration: number, events: InjectedEvent[], stackEvents: StackEvent[], - resources: Resource[], - missedResources: Resource[], metadata: [], favorite: boolean, filterId?: string, @@ -119,7 +116,6 @@ export default class Session { duration: ISession["duration"] events: ISession["events"] stackEvents: ISession["stackEvents"] - resources: ISession["resources"] metadata: ISession["metadata"] favorite: ISession["favorite"] filterId?: ISession["filterId"] @@ -181,7 +177,6 @@ export default class Session { devtoolsURL = [], mobsUrl = [], notes = [], - resources = [], ...session } = sessionData const duration = Duration.fromMillis(session.duration < 1000 ? 1000 : session.duration); @@ -208,13 +203,6 @@ export default class Session { }) } - let resourcesList = resources.map((r) => new Resource(r as any)); - resourcesList.forEach((r: Resource) => { - r.time = Math.max(0, r.time - startedAt) - }) - resourcesList = resourcesList.sort((r1, r2) => r1.time - r2.time); - const missedResources = resourcesList.filter(({ success }) => !success); - const stackEventsList: StackEvent[] = [] if (stackEvents?.length || session.userEvents?.length) { const mergedArrays = [...stackEvents, ...session.userEvents] @@ -245,8 +233,6 @@ export default class Session { siteId: projectId, events, stackEvents: stackEventsList, - resources: resourcesList, - missedResources, userDevice, userDeviceType, isMobile, diff --git a/frontend/app/types/synthetics/domBuildingTime.js b/frontend/app/types/synthetics/domBuildingTime.js deleted file mode 100644 index 258902f60..000000000 --- a/frontend/app/types/synthetics/domBuildingTime.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Record } from 'immutable'; - -const DomBuildingTime = Record({ - avg: undefined, - chart: [], -}); - - -function fromJS(data = {}) { - if (data instanceof DomBuildingTime) return data; - return new DomBuildingTime(data); -} - -export default fromJS; \ No newline at end of file diff --git a/frontend/app/types/synthetics/index.js b/frontend/app/types/synthetics/index.js deleted file mode 100644 index 1cec8da81..000000000 --- a/frontend/app/types/synthetics/index.js +++ /dev/null @@ -1,27 +0,0 @@ -import { getChartFormatter } from './helper'; -import DomBuildingTime from './domBuildingTime'; - -export const WIDGET_LIST = [ - { - key: "resourcesLoadingTime", - name: "Resource Fetch Time", - description: 'List of resources that are slowing down your website, sorted by the number of impacted sessions.', - thumb: 'na.png', - type: 'resources', - dataWrapper: (list, period) => DomBuildingTime(list) - .update("chart", getChartFormatter(period)) - }, -]; - -export const WIDGET_KEYS = WIDGET_LIST.map(({ key }) => key); - -const WIDGET_MAP = {}; -WIDGET_LIST.forEach(w => { WIDGET_MAP[ w.key ] = w; }); - -const OVERVIEW_WIDGET_MAP = {}; -WIDGET_LIST.filter(w => w.type === 'overview').forEach(w => { OVERVIEW_WIDGET_MAP[ w.key ] = w; }); - -export { - WIDGET_MAP, - OVERVIEW_WIDGET_MAP -}; diff --git a/frontend/app/utils/index.ts b/frontend/app/utils/index.ts index b1ae63f43..47b40aa70 100644 --- a/frontend/app/utils/index.ts +++ b/frontend/app/utils/index.ts @@ -17,13 +17,6 @@ export function debounce(callback, wait, context = this) { }; } -export function getResourceName(url: string) { - return url - .split('/') - .filter((s) => s !== '') - .pop(); -} - /* eslint-disable no-mixed-operators */ export function randomInt(a, b) { const min = (b ? a : 0) - 0.5;