From 9a7feb24b9f58e8899fc13d1e7cc30dade58d297 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 22 Sep 2022 13:22:22 +0530 Subject: [PATCH 01/15] change(ui) - search changes --- .../Filters/FilterSource/FilterSource.tsx | 2 +- .../Filters/FilterValue/FilterValue.tsx | 6 ++-- .../FilterValueDropdown.tsx | 5 +-- frontend/app/types/filter/newFilter.js | 32 ++++++++++--------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx index 0975f7335..eed1e6e1d 100644 --- a/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx +++ b/frontend/app/components/shared/Filters/FilterSource/FilterSource.tsx @@ -28,7 +28,7 @@ function FilterSource(props: Props) { case FilterType.NUMBER: return (
- +
{filter.sourceUnit}
); diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 5638f9a1d..f9e9dea2e 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -93,7 +93,8 @@ function FilterValue(props: Props) { onChange(null, { value }, valueIndex)} /> @@ -106,6 +107,7 @@ function FilterValue(props: Props) { // multiple={true} value={value} // filter={filter} + placeholder={filter.placeholder} options={filter.options} onChange={({ value }) => onChange(null, value, valueIndex)} onAddValue={onAddValue} @@ -164,7 +166,7 @@ function FilterValue(props: Props) { endpoint="/events/search" params={getParms(filter.key)} headerText={''} - // placeholder={''} + placeholder={filter.placeholder} onSelect={(e, item) => onChange(e, item, valueIndex)} icon={filter.icon} /> diff --git a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx index 13c40cbd8..28e1d23a1 100644 --- a/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx +++ b/frontend/app/components/shared/Filters/FilterValueDropdown/FilterValueDropdown.tsx @@ -71,6 +71,7 @@ const dropdownStyles = { interface Props { // filter: any; // event/filter // options: any[]; + placeholder?: string value: string; onChange: (value: any) => void; className?: string; @@ -84,7 +85,7 @@ interface Props { isMultilple?: boolean; } function FilterValueDropdown(props: Props) { - const { isMultilple = true, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props; + const { placeholder = 'Select', isMultilple = true, search = false, options, onChange, value, className = '', showCloseButton = true, showOrButton = true } = props; // const options = [] return ( @@ -97,7 +98,7 @@ function FilterValueDropdown(props: Props) { name="issue_type" defaultValue={ value } onChange={ (value: any) => onChange(value.value) } - placeholder="Select" + placeholder={placeholder} styles={dropdownStyles} />
=', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, - { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '>=', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, - { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '>=', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, - { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceUnit: '%', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, - { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourceUnit: 'mb', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, - { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, icon: 'filters/fetch-failed', isEvent: true }, - { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, + { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, + { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, + { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '>=', sourceUnit: 'ms', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators, sourcePlaceholder: 'E.g. 12', }, + { key: FilterKey.AVG_CPU_LOAD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg CPU Load', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/cpu-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: '%', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, + { key: FilterKey.AVG_MEMORY_USAGE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Avg Memory Usage', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, source: [], icon: 'filters/memory-load', isEvent: true, hasSource: true, sourceOperator: '>=', sourcePlaceholder: 'E.g. 12', sourceUnit: 'mb', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators }, + { key: FilterKey.FETCH_FAILED, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Failed Request', placeholder: 'Enter path', operator: 'isAny', operatorOptions: filterOptions.stringOperatorsPerformance, icon: 'filters/fetch-failed', isEvent: true }, + { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', placeholder: 'Select an issue', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/click', options: filterOptions.issueOptions }, ]; const mapFilters = (list) => { @@ -137,6 +137,7 @@ export default Record({ timestamp: 0, key: '', label: '', + placeholder: '', icon: '', type: '', value: [""], @@ -155,6 +156,7 @@ export default Record({ source: [""], sourceType: '', sourceOperator: '=', + sourcePlaceholder: '', sourceUnit: '', sourceOperatorOptions: [], From b0b99e4910f8cfe88dd9c1dcfc5582625bbb1d96 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 22 Sep 2022 15:42:47 +0530 Subject: [PATCH 02/15] change(ui) - search changes --- frontend/app/types/filter/newFilter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index cd65aa1f6..80fe80c3d 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -27,7 +27,7 @@ export const filters = [ { key: FilterKey.GRAPHQL_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, ]}, { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'State Action', placeholder: 'E.g. 12', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/state-action', isEvent: true }, - { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true }, + { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error Message', placeholder: 'E.g. ', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true }, // { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true }, // FILTERS @@ -35,12 +35,12 @@ export const filters = [ { key: FilterKey.USER_BROWSER, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Browser', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/browser' }, { key: FilterKey.USER_DEVICE, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'User Device', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/device' }, { key: FilterKey.PLATFORM, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.GEAR, label: 'Platform', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/platform', options: platformOptions }, - { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'Version ID', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'collection' }, + { key: FilterKey.REVID, type: FilterType.MULTIPLE, category: FilterCategory.GEAR, label: 'Version ID', placeholder: 'E.g. v1.0.8', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'collection' }, { key: FilterKey.REFERRER, type: FilterType.MULTIPLE, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Referrer', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/arrow-return-right' }, { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' }, { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.USER, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions }, // { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' }, - { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators.concat([{ label: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' }, + { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'Identifier (User ID, Name, Email, etc)', placeholder: 'E.g. Alex, or alex@domain.com, or EMP123', operator: 'is', operatorOptions: filterOptions.stringOperators.concat([{ label: 'is undefined', value: 'isUndefined'}]), icon: 'filters/userid' }, { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, // PERFORMANCE From 17ef16406ee952e95576a81c6a63ed53f49ba504 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 22 Sep 2022 15:44:27 +0530 Subject: [PATCH 03/15] change(ui) - search changes --- frontend/app/types/filter/newFilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 80fe80c3d..675298ae6 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -27,7 +27,7 @@ export const filters = [ { key: FilterKey.GRAPHQL_RESPONSE_BODY, type: FilterType.STRING, category: FilterCategory.PERFORMANCE, label: 'with response body', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' }, ]}, { key: FilterKey.STATEACTION, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'State Action', placeholder: 'E.g. 12', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/state-action', isEvent: true }, - { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error Message', placeholder: 'E.g. ', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true }, + { key: FilterKey.ERROR, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Error Message', placeholder: 'E.g. Uncaught SyntaxError', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/error', isEvent: true }, // { key: FilterKey.METADATA, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: 'Metadata', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata', isEvent: true }, // FILTERS From 959cf44cad2c521b7fbea881925559649f323330 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 27 Sep 2022 13:35:32 +0530 Subject: [PATCH 04/15] remote dev pull and resolved conflicts --- .../Alerts/Notifications/Notifications.tsx | 2 +- frontend/app/components/Header/Header.js | 105 ++++++++++-------- .../components/Header/UserMenu/UserMenu.tsx | 53 +++++++++ .../app/components/Header/UserMenu/index.ts | 1 + .../app/components/Header/header.module.css | 5 +- .../components/Header/siteDropdown.module.css | 2 +- frontend/app/components/ui/SVG.tsx | 4 +- frontend/app/svg/icons/bell-fill.svg | 3 + frontend/app/svg/icons/gear-fill.svg | 3 + 9 files changed, 123 insertions(+), 55 deletions(-) create mode 100644 frontend/app/components/Header/UserMenu/UserMenu.tsx create mode 100644 frontend/app/components/Header/UserMenu/index.ts create mode 100644 frontend/app/svg/icons/bell-fill.svg create mode 100644 frontend/app/svg/icons/gear-fill.svg diff --git a/frontend/app/components/Alerts/Notifications/Notifications.tsx b/frontend/app/components/Alerts/Notifications/Notifications.tsx index d6327d530..1ba2ce49f 100644 --- a/frontend/app/components/Alerts/Notifications/Notifications.tsx +++ b/frontend/app/components/Alerts/Notifications/Notifications.tsx @@ -34,7 +34,7 @@ function Notifications(props: Props) {
{ count }
- +
)); diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 6159197b1..429f5c80d 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -16,10 +16,11 @@ import { logout } from 'Duck/user'; import { Icon, Popup } from 'UI'; import SiteDropdown from './SiteDropdown'; import styles from './header.module.css'; -import OnboardingExplore from './OnboardingExplore/OnboardingExplore' +import OnboardingExplore from './OnboardingExplore/OnboardingExplore'; import Announcements from '../Announcements'; import Notifications from '../Alerts/Notifications'; import { init as initSite } from 'Duck/site'; +import { getInitials } from 'App/utils'; import ErrorGenPanel from 'App/dev/components'; import Alerts from '../Alerts/Alerts'; @@ -27,6 +28,7 @@ import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG'; import { fetchListActive as fetchMetadata } from 'Duck/customField'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; +import UserMenu from './UserMenu'; const DASHBOARD_PATH = dashboard(); const ALERTS_PATH = alerts(); @@ -37,20 +39,24 @@ const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); const Header = (props) => { const { - sites, location, account, - onLogoutClick, siteId, - boardingCompletion = 100, showAlerts = false, + sites, + location, + account, + onLogoutClick, + siteId, + boardingCompletion = 100, + showAlerts = false, } = props; - const name = account.get('name').split(" ")[0]; - const [hideDiscover, setHideDiscover] = useState(false) + const name = account.get('name').split(' ')[0]; + const [hideDiscover, setHideDiscover] = useState(false); const { userStore, notificationStore } = useStore(); const initialDataFetched = useObserver(() => userStore.initialDataFetched); let activeSite = null; const onAccountClick = () => { props.history.push(CLIENT_PATH); - } + }; useEffect(() => { if (!account.id || initialDataFetched) return; @@ -67,36 +73,38 @@ const Header = (props) => { }, [account]); useEffect(() => { - activeSite = sites.find(s => s.id == siteId); + activeSite = sites.find((s) => s.id == siteId); props.initSite(activeSite); - }, [siteId]) + }, [siteId]); return ( -
- +
+
-
v{window.env.VERSION}
+
+ v{window.env.VERSION} +
-
+ {/*
*/} - { 'Sessions' } + {'Sessions'} - { 'Assist' } + {'Assist'} { || location.pathname.includes(ALERTS_PATH) }} > - { 'Dashboards' } + {'Dashboards'} -
- -
+
+ {/* */} + {/*
*/} - { (boardingCompletion < 100 && !hideDiscover) && ( + {boardingCompletion < 100 && !hideDiscover && ( setHideDiscover(true)} /> -
)} -
- - + + + + -
-
+
-
{ name }
- +
+ {getInitials(name)} +
-
    -
  • -
  • -
+
- { } + {} {showAlerts && }
); }; -export default withRouter(connect( - state => ({ - account: state.getIn([ 'user', 'account' ]), - siteId: state.getIn([ 'site', 'siteId' ]), - sites: state.getIn([ 'site', 'list' ]), - showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]), - boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]) - }), - { onLogoutClick: logout, initSite, fetchMetadata }, -)(Header)); +export default withRouter( + connect( + (state) => ({ + account: state.getIn(['user', 'account']), + siteId: state.getIn(['site', 'siteId']), + sites: state.getIn(['site', 'list']), + showAlerts: state.getIn(['dashboard', 'showAlerts']), + boardingCompletion: state.getIn(['dashboard', 'boardingCompletion']), + }), + { onLogoutClick: logout, initSite, fetchMetadata } + )(Header) +); diff --git a/frontend/app/components/Header/UserMenu/UserMenu.tsx b/frontend/app/components/Header/UserMenu/UserMenu.tsx new file mode 100644 index 000000000..ee369cae7 --- /dev/null +++ b/frontend/app/components/Header/UserMenu/UserMenu.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { logout } from 'Duck/user'; +import { client, CLIENT_DEFAULT_TAB } from 'App/routes'; +import cn from 'classnames'; + +const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); + +interface Props { + history: any; + onLogoutClick: any; + className: string; +} +function UserMenu(props: Props) { + const onAccountClick = () => { + props.history.push(CLIENT_PATH); + }; + return ( +
+
+
+ SS +
+
+
User Name
+
Admin - admin@gmail.com
+
+
+
+ +
+
+ +
+
+ ); +} + +export default withRouter( + connect( + (state: any) => ({ + // account: state.getIn([ 'user', 'account' ]), + // siteId: state.getIn([ 'site', 'siteId' ]), + // sites: state.getIn([ 'site', 'list' ]), + // showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]), + // boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]) + }), + { onLogoutClick: logout } + )(UserMenu) +); + +// export default UserMenu; diff --git a/frontend/app/components/Header/UserMenu/index.ts b/frontend/app/components/Header/UserMenu/index.ts new file mode 100644 index 000000000..eeb4a1e9e --- /dev/null +++ b/frontend/app/components/Header/UserMenu/index.ts @@ -0,0 +1 @@ +export { default } from './UserMenu'; \ No newline at end of file diff --git a/frontend/app/components/Header/header.module.css b/frontend/app/components/Header/header.module.css index 9852b7436..f24561aa3 100644 --- a/frontend/app/components/Header/header.module.css +++ b/frontend/app/components/Header/header.module.css @@ -72,11 +72,10 @@ $height: 50px; .userDetails { display: flex; align-items: center; - justify-content: flex-end; + justify-content: center; position: relative; - padding: 0 5px 0 15px; + padding: 0 10px; transition: all 0.2s; - min-width: 100px; &:hover { background-color: $gray-lightest; diff --git a/frontend/app/components/Header/siteDropdown.module.css b/frontend/app/components/Header/siteDropdown.module.css index 886c60016..769f07f29 100644 --- a/frontend/app/components/Header/siteDropdown.module.css +++ b/frontend/app/components/Header/siteDropdown.module.css @@ -1,7 +1,7 @@ .wrapper { display: flex; align-items: center; - border-left: solid thin $gray-light !important; + /* border-left: solid thin $gray-light !important; */ padding: 10px 10px; min-width: 180px; justify-content: flex-start; diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 23aa27e70..7ab78788c 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-clockwise' | 'arrow-down' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-plus' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'lock-alt' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-clockwise' | 'arrow-down' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'lock-alt' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -67,6 +67,7 @@ const SVG = (props: Props) => { case 'ban': return ; case 'bar-chart-line': return ; case 'bar-pencil': return ; + case 'bell-fill': return ; case 'bell-plus': return ; case 'bell': return ; case 'binoculars': return ; @@ -224,6 +225,7 @@ const SVG = (props: Props) => { case 'funnel-fill': return ; case 'funnel-new': return ; case 'funnel': return ; + case 'gear-fill': return ; case 'geo-alt-fill-custom': return ; case 'github': return ; case 'graph-up-arrow': return ; diff --git a/frontend/app/svg/icons/bell-fill.svg b/frontend/app/svg/icons/bell-fill.svg new file mode 100644 index 000000000..b5fa6a05b --- /dev/null +++ b/frontend/app/svg/icons/bell-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/gear-fill.svg b/frontend/app/svg/icons/gear-fill.svg new file mode 100644 index 000000000..ac2a31086 --- /dev/null +++ b/frontend/app/svg/icons/gear-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From d22fa1efc16ce16cf55065e2cd8d86d3d4f19879 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 23 Sep 2022 19:31:05 +0530 Subject: [PATCH 05/15] fix(ui) - error parse --- frontend/app/api_middleware.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/app/api_middleware.js b/frontend/app/api_middleware.js index 783ebe8c3..1846a9dbc 100644 --- a/frontend/app/api_middleware.js +++ b/frontend/app/api_middleware.js @@ -2,27 +2,27 @@ import logger from 'App/logger'; import APIClient from './api_client'; import { UPDATE, DELETE } from './duck/jwt'; -export default store => next => (action) => { +export default (store) => (next) => (action) => { const { types, call, ...rest } = action; if (!call) { return next(action); } - const [ REQUEST, SUCCESS, FAILURE ] = types; + const [REQUEST, SUCCESS, FAILURE] = types; next({ ...rest, type: REQUEST }); const client = new APIClient(); return call(client) - .then(async response => { + .then(async (response) => { if (response.status === 403) { next({ type: DELETE }); } if (!response.ok) { - const text = await response.text() + const text = await response.text(); return Promise.reject(text); } - return response.json() + return response.json(); }) - .then(json => json || {}) // TEMP TODO on server: no empty responces + .then((json) => json || {}) // TEMP TODO on server: no empty responces .then(({ jwt, errors, data }) => { if (errors) { next({ type: FAILURE, errors, data }); @@ -34,14 +34,22 @@ export default store => next => (action) => { } }) .catch((e) => { - logger.error("Error during API request. ", e) - return next({ type: FAILURE, errors: JSON.parse(e).errors || [] }); + logger.error('Error during API request. ', e); + return next({ type: FAILURE, errors: parseError(e) }); }); }; +function parseError(e) { + try { + return JSON.parse(e).errors || []; + } catch { + return e; + } +} + function jwtExpired(token) { try { - const base64Url = token.split('.')[ 1 ]; + const base64Url = token.split('.')[1]; const base64 = base64Url.replace('-', '+').replace('_', '/'); const tokenObj = JSON.parse(window.atob(base64)); return tokenObj.exp * 1000 < Date.now(); // exp in Unix time (sec) From 9b6be7e580442962eb9ff0d33630deef3903a2c5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 27 Sep 2022 13:37:39 +0530 Subject: [PATCH 06/15] remote dev pull and resolved conflicts --- .../DefaultMenuView/DefaultMenuView.tsx | 71 +++++++++++++++++++ .../Header/DefaultMenuView/index.ts | 1 + frontend/app/components/Header/Header.js | 71 ++++++------------- .../NewProjectButton/NewProjectButton.tsx | 18 +++-- .../PreferencesView/PreferencesView.tsx | 28 ++++++++ .../Header/PreferencesView/index.ts | 1 + .../Header/SettingsMenu/SettingsMenu.tsx | 66 +++++++++++++++++ .../components/Header/SettingsMenu/index.ts | 1 + .../app/components/Header/SiteDropdown.js | 22 ++---- .../components/Header/UserMenu/UserMenu.tsx | 50 +++++++------ .../app/components/Header/header.module.css | 16 ++--- .../components/Header/siteDropdown.module.css | 18 +++-- frontend/app/components/ui/SVG.tsx | 8 ++- frontend/app/svg/icons/arrow-bar-left.svg | 3 + frontend/app/svg/icons/bell-slash.svg | 3 + frontend/app/svg/icons/door-closed.svg | 4 ++ frontend/app/svg/icons/folder-plus.svg | 4 ++ frontend/app/svg/icons/folder2.svg | 3 + frontend/app/svg/icons/puzzle.svg | 3 + 19 files changed, 282 insertions(+), 109 deletions(-) create mode 100644 frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx create mode 100644 frontend/app/components/Header/DefaultMenuView/index.ts create mode 100644 frontend/app/components/Header/PreferencesView/PreferencesView.tsx create mode 100644 frontend/app/components/Header/PreferencesView/index.ts create mode 100644 frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx create mode 100644 frontend/app/components/Header/SettingsMenu/index.ts create mode 100644 frontend/app/svg/icons/arrow-bar-left.svg create mode 100644 frontend/app/svg/icons/bell-slash.svg create mode 100644 frontend/app/svg/icons/door-closed.svg create mode 100644 frontend/app/svg/icons/folder-plus.svg create mode 100644 frontend/app/svg/icons/folder2.svg create mode 100644 frontend/app/svg/icons/puzzle.svg diff --git a/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx new file mode 100644 index 000000000..a8b321c8c --- /dev/null +++ b/frontend/app/components/Header/DefaultMenuView/DefaultMenuView.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { NavLink, withRouter } from 'react-router-dom'; +import { + sessions, + metrics, + assist, + client, + dashboard, + withSiteId, + CLIENT_DEFAULT_TAB, +} from 'App/routes'; +import SiteDropdown from '../SiteDropdown'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import styles from '../header.module.css'; + +const DASHBOARD_PATH = dashboard(); +const METRICS_PATH = metrics(); +const SESSIONS_PATH = sessions(); +const ASSIST_PATH = assist(); + +interface Props { + siteId: any; +} +function DefaultMenuView(props: Props) { + const { siteId } = props; + return ( +
+ +
+
+ +
+
+ v{window.env.VERSION} +
+
+
+ + {/*
*/} + + + {'Sessions'} + + + {'Assist'} + + { + return ( + location.pathname.includes(DASHBOARD_PATH) || location.pathname.includes(METRICS_PATH) + ); + }} + > + {'Dashboards'} + +
+ ); +} + +export default DefaultMenuView; diff --git a/frontend/app/components/Header/DefaultMenuView/index.ts b/frontend/app/components/Header/DefaultMenuView/index.ts new file mode 100644 index 000000000..8b21e4cfb --- /dev/null +++ b/frontend/app/components/Header/DefaultMenuView/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultMenuView' \ No newline at end of file diff --git a/frontend/app/components/Header/Header.js b/frontend/app/components/Header/Header.js index 429f5c80d..bd89b176d 100644 --- a/frontend/app/components/Header/Header.js +++ b/frontend/app/components/Header/Header.js @@ -29,6 +29,9 @@ import { fetchListActive as fetchMetadata } from 'Duck/customField'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import UserMenu from './UserMenu'; +import SettingsMenu from './SettingsMenu'; +import DefaultMenuView from './DefaultMenuView'; +import PreferencesView from './PreferencesView'; const DASHBOARD_PATH = dashboard(); const ALERTS_PATH = alerts(); @@ -48,11 +51,12 @@ const Header = (props) => { showAlerts = false, } = props; - const name = account.get('name').split(' ')[0]; + const name = account.get('name'); const [hideDiscover, setHideDiscover] = useState(false); const { userStore, notificationStore } = useStore(); const initialDataFetched = useObserver(() => userStore.initialDataFetched); let activeSite = null; + const isPreferences = window.location.pathname.includes('/client/'); const onAccountClick = () => { props.history.push(CLIENT_PATH); @@ -78,50 +82,13 @@ const Header = (props) => { }, [siteId]); return ( -
- -
-
- -
-
- v{window.env.VERSION} -
-
-
- - {/*
*/} - - - {'Sessions'} - - - {'Assist'} - - { - return location.pathname.includes(DASHBOARD_PATH) - || location.pathname.includes(METRICS_PATH) - || location.pathname.includes(ALERTS_PATH) - }} - > - {'Dashboards'} - +
+ {!isPreferences && } + {isPreferences && }
- {/* */} - {/*
*/} - {boardingCompletion < 100 && !hideDiscover && ( setHideDiscover(true)} /> @@ -129,10 +96,14 @@ const Header = (props) => { )} - - - - + +
+ + + + + +
@@ -144,8 +115,10 @@ const Header = (props) => {
+ + {}
- {} + {showAlerts && }
); diff --git a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx index 9c438c50f..a0dd30244 100644 --- a/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx +++ b/frontend/app/components/Header/NewProjectButton/NewProjectButton.tsx @@ -24,13 +24,17 @@ function NewProjectButton(props: Props) { }; return ( -
- - Add New Project -
+
  • + + Add Project +
  • + //
    + // + // Add New Project + //
    ); } diff --git a/frontend/app/components/Header/PreferencesView/PreferencesView.tsx b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx new file mode 100644 index 000000000..0ba533350 --- /dev/null +++ b/frontend/app/components/Header/PreferencesView/PreferencesView.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { withRouter } from 'react-router-dom'; +import ProjectCodeSnippet from 'App/components/Onboarding/components/OnboardingTabs/ProjectCodeSnippet'; + +interface Props { + history: any; +} +function PreferencesView(props: Props) { + const onExit = () => { + props.history.push('/'); + }; + return ( + <> +
    + + Exit Preferences +
    + +
    + + Changes applied at organization level +
    + + ); +} + +export default withRouter(PreferencesView); diff --git a/frontend/app/components/Header/PreferencesView/index.ts b/frontend/app/components/Header/PreferencesView/index.ts new file mode 100644 index 000000000..774801dcc --- /dev/null +++ b/frontend/app/components/Header/PreferencesView/index.ts @@ -0,0 +1 @@ +export { default } from './PreferencesView'; \ No newline at end of file diff --git a/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx new file mode 100644 index 000000000..58bc09b8c --- /dev/null +++ b/frontend/app/components/Header/SettingsMenu/SettingsMenu.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; +import { CLIENT_TABS, client as clientRoute } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router'; + +interface Props { + history: any; + className: string; + account: any; +} +function SettingsMenu(props: RouteComponentProps) { + const { history, account, className }: any = props; + const isAdmin = account.admin || account.superAdmin; + const navigateTo = (path: any) => { + switch (path) { + case 'projects': + return history.push(clientRoute(CLIENT_TABS.SITES)); + case 'team': + return history.push(clientRoute(CLIENT_TABS.MANAGE_USERS)); + case 'metadata': + return history.push(clientRoute(CLIENT_TABS.CUSTOM_FIELDS)); + case 'webhooks': + return history.push(clientRoute(CLIENT_TABS.WEBHOOKS)); + case 'integrations': + return history.push(clientRoute(CLIENT_TABS.INTEGRATIONS)); + case 'notifications': + return history.push(clientRoute(CLIENT_TABS.NOTIFICATIONS)); + } + }; + return ( +
    + {isAdmin && ( + <> + navigateTo('projects')} label="Projects" icon="folder2" /> + navigateTo('team')} label="Team" icon="users" /> + + )} + navigateTo('metadata')} label="Metadata" icon="tags" /> + navigateTo('webhooks')} label="Webhooks" icon="link-45deg" /> + navigateTo('integrations')} label="Integrations" icon="puzzle" /> + navigateTo('notifications')} + label="Notifications" + icon="bell-slash" + /> +
    + ); +} + +export default withRouter(SettingsMenu); + +function MenuItem({ onClick, label, icon }: any) { + return ( +
    + + +
    + ); +} diff --git a/frontend/app/components/Header/SettingsMenu/index.ts b/frontend/app/components/Header/SettingsMenu/index.ts new file mode 100644 index 000000000..0133de70a --- /dev/null +++ b/frontend/app/components/Header/SettingsMenu/index.ts @@ -0,0 +1 @@ +export { default } from './SettingsMenu'; \ No newline at end of file diff --git a/frontend/app/components/Header/SiteDropdown.js b/frontend/app/components/Header/SiteDropdown.js index 7a0205be3..90d024910 100644 --- a/frontend/app/components/Header/SiteDropdown.js +++ b/frontend/app/components/Header/SiteDropdown.js @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; 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'; import { Icon } from 'UI'; import { pushNewSite } from 'Duck/user'; import { init } from 'Duck/site'; @@ -13,7 +12,6 @@ import { clearSearch } from 'Duck/search'; import { clearSearch as clearSearchLive } from 'Duck/liveSearch'; import { fetchListActive as fetchIntegrationVariables } from 'Duck/customField'; import { withStore } from 'App/mstore'; -import AnimatedSVG, { ICONS } from '../shared/AnimatedSVG/AnimatedSVG'; import NewProjectButton from './NewProjectButton'; @withStore @@ -63,37 +61,27 @@ export default class SiteDropdown extends React.PureComponent { account, location: { pathname }, } = this.props; - const { showProductModal } = this.state; const isAdmin = account.admin || account.superAdmin; const activeSite = sites.find((s) => s.id == siteId); const disabled = !siteChangeAvaliable(pathname); const showCurrent = hasSiteId(pathname) || siteChangeAvaliable(pathname); - // const canAddSites = isAdmin && account.limits.projects && account.limits.projects.remaining !== 0; return (
    - {showCurrent ? ( - activeSite && activeSite.status === GREEN ? ( - - ) : ( - - ) - ) : ( - - )}
    {showCurrent && activeSite ? activeSite.host : 'All Projects'}
      - {!showCurrent &&
    • {'Project selection is not applicable.'}
    • } + {isAdmin && ( + + )} {sites.map((site) => (
    • this.switchSite(site.id)}> -
      - {site.host} + + {site.host}
    • ))}
    -
    ); diff --git a/frontend/app/components/Header/UserMenu/UserMenu.tsx b/frontend/app/components/Header/UserMenu/UserMenu.tsx index ee369cae7..c88f7ff1a 100644 --- a/frontend/app/components/Header/UserMenu/UserMenu.tsx +++ b/frontend/app/components/Header/UserMenu/UserMenu.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { connect } from 'react-redux'; import { logout } from 'Duck/user'; import { client, CLIENT_DEFAULT_TAB } from 'App/routes'; +import { Icon } from 'UI'; import cn from 'classnames'; +import { getInitials } from 'App/utils'; const CLIENT_PATH = client(CLIENT_DEFAULT_TAB); @@ -11,43 +13,45 @@ interface Props { history: any; onLogoutClick: any; className: string; + account: any; } -function UserMenu(props: Props) { +function UserMenu(props: RouteComponentProps) { + const { account, history, className, onLogoutClick }: any = props; + const onAccountClick = () => { - props.history.push(CLIENT_PATH); + history.push(CLIENT_PATH); }; return ( -
    +
    - SS + {getInitials(account.name)}
    -
    User Name
    -
    Admin - admin@gmail.com
    +
    {account.name}
    +
    {account.superAdmin ? 'Super Admin' : (account.admin ? 'Admin' : 'Member') } - {account.email}
    -
    - +
    + +
    -
    - +
    + +
    ); } -export default withRouter( - connect( - (state: any) => ({ - // account: state.getIn([ 'user', 'account' ]), - // siteId: state.getIn([ 'site', 'siteId' ]), - // sites: state.getIn([ 'site', 'list' ]), - // showAlerts: state.getIn([ 'dashboard', 'showAlerts' ]), - // boardingCompletion: state.getIn([ 'dashboard', 'boardingCompletion' ]) - }), - { onLogoutClick: logout } - )(UserMenu) -); +export default connect( + (state: any) => ({ + account: state.getIn(['user', 'account']), + }), + { onLogoutClick: logout } +)(withRouter(UserMenu)) as React.FunctionComponent>; // export default UserMenu; diff --git a/frontend/app/components/Header/header.module.css b/frontend/app/components/Header/header.module.css index f24561aa3..5b2bbd46f 100644 --- a/frontend/app/components/Header/header.module.css +++ b/frontend/app/components/Header/header.module.css @@ -4,13 +4,13 @@ $height: 50px; .header { - position: fixed; - width: 100%; - display: flex; - justify-content: space-between; + /* position: fixed; */ + /* width: 100%; */ + /* display: flex; */ + /* justify-content: space-between; */ border-bottom: solid thin $gray-light; /* padding: 0 15px; */ - background: $white; + /* background: $white; */ z-index: $header; } @@ -45,7 +45,7 @@ $height: 50px; } .right { - margin-left: auto; + /* margin-left: auto; */ position: relative; cursor: default; display: flex; @@ -101,7 +101,7 @@ $height: 50px; border-top: 1px solid $gray-light; } } - & a, & button { + /* & a, & button { color: $gray-darkest; display: block; cursor: pointer; @@ -112,7 +112,7 @@ $height: 50px; &:hover { background-color: $gray-lightest; } - } + } */ } .userIcon { diff --git a/frontend/app/components/Header/siteDropdown.module.css b/frontend/app/components/Header/siteDropdown.module.css index 769f07f29..508eb81c8 100644 --- a/frontend/app/components/Header/siteDropdown.module.css +++ b/frontend/app/components/Header/siteDropdown.module.css @@ -7,9 +7,14 @@ justify-content: flex-start; position: relative; user-select: none; - + border: solid thin transparent; + height: 30px; + border-radius: 3px; + margin: 10px; + &:hover { - background-color: $gray-lightest; + background-color: $active-blue; + /* border: solid thin $active-blue-border; */ & .drodownIcon { transform: rotate(180deg); transition: all 0.2s; @@ -39,11 +44,12 @@ & .menu { display: none; position: absolute; - top: 50px; + top: 28px; left: -1px; background-color: white; min-width: 200px; z-index: 2; + border-radius: 3px; border: 1px solid $gray-light; } @@ -68,9 +74,13 @@ &:hover { background-color: $gray-lightest; transition: all 0.2s; + color: $teal; + svg { + fill: $teal; + } } &:first-child { - border-top: 1px solid $gray-light; + /* border-top: 1px solid $gray-light; */ } } } diff --git a/frontend/app/components/ui/SVG.tsx b/frontend/app/components/ui/SVG.tsx index 7ab78788c..b12dd305b 100644 --- a/frontend/app/components/ui/SVG.tsx +++ b/frontend/app/components/ui/SVG.tsx @@ -1,7 +1,7 @@ import React from 'react'; -export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-clockwise' | 'arrow-down' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'lock-alt' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; +export type IconNames = 'alarm-clock' | 'alarm-plus' | 'all-sessions' | 'analytics' | 'anchor' | 'arrow-alt-square-right' | 'arrow-bar-left' | 'arrow-clockwise' | 'arrow-down' | 'arrow-right-short' | 'arrow-square-left' | 'arrow-square-right' | 'arrow-up' | 'arrows-angle-extend' | 'avatar/icn_bear' | 'avatar/icn_beaver' | 'avatar/icn_bird' | 'avatar/icn_bison' | 'avatar/icn_camel' | 'avatar/icn_chameleon' | 'avatar/icn_deer' | 'avatar/icn_dog' | 'avatar/icn_dolphin' | 'avatar/icn_elephant' | 'avatar/icn_fish' | 'avatar/icn_fox' | 'avatar/icn_gorilla' | 'avatar/icn_hippo' | 'avatar/icn_horse' | 'avatar/icn_hyena' | 'avatar/icn_kangaroo' | 'avatar/icn_lemur' | 'avatar/icn_mammel' | 'avatar/icn_monkey' | 'avatar/icn_moose' | 'avatar/icn_panda' | 'avatar/icn_penguin' | 'avatar/icn_porcupine' | 'avatar/icn_quail' | 'avatar/icn_rabbit' | 'avatar/icn_rhino' | 'avatar/icn_sea_horse' | 'avatar/icn_sheep' | 'avatar/icn_snake' | 'avatar/icn_squirrel' | 'avatar/icn_tapir' | 'avatar/icn_turtle' | 'avatar/icn_vulture' | 'avatar/icn_wild1' | 'avatar/icn_wild_bore' | 'ban' | 'bar-chart-line' | 'bar-pencil' | 'bell-fill' | 'bell-plus' | 'bell-slash' | 'bell' | 'binoculars' | 'book' | 'browser/browser' | 'browser/chrome' | 'browser/edge' | 'browser/electron' | 'browser/facebook' | 'browser/firefox' | 'browser/ie' | 'browser/opera' | 'browser/safari' | 'bullhorn' | 'business-time' | 'calendar-alt' | 'calendar-check' | 'calendar-day' | 'calendar' | 'call' | 'camera-alt' | 'camera-video-off' | 'camera-video' | 'camera' | 'caret-down-fill' | 'caret-left-fill' | 'caret-right-fill' | 'caret-up-fill' | 'chat-dots' | 'chat-right-text' | 'chat-square-quote' | 'check-circle' | 'check' | 'chevron-double-left' | 'chevron-double-right' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-fill' | 'circle' | 'clipboard-list-check' | 'clock' | 'close' | 'cloud-fog2-fill' | 'code' | 'cog' | 'cogs' | 'collection' | 'columns-gap-filled' | 'columns-gap' | 'console/error' | 'console/exception' | 'console/info' | 'console/warning' | 'console' | 'controller' | 'cookies' | 'copy' | 'credit-card-front' | 'cubes' | 'dashboard-icn' | 'desktop' | 'device' | 'diagram-3' | 'dizzy' | 'door-closed' | 'doublecheck' | 'download' | 'drag' | 'edit' | 'ellipsis-v' | 'enter' | 'envelope' | 'errors-icon' | 'event/click' | 'event/clickrage' | 'event/code' | 'event/i-cursor' | 'event/input' | 'event/link' | 'event/location' | 'event/resize' | 'event/view' | 'exclamation-circle' | 'expand-wide' | 'explosion' | 'external-link-alt' | 'eye-slash-fill' | 'eye-slash' | 'eye' | 'fetch' | 'file-code' | 'file-medical-alt' | 'file' | 'filter' | 'filters/arrow-return-right' | 'filters/browser' | 'filters/click' | 'filters/clickrage' | 'filters/code' | 'filters/console' | 'filters/country' | 'filters/cpu-load' | 'filters/custom' | 'filters/device' | 'filters/dom-complete' | 'filters/duration' | 'filters/error' | 'filters/fetch-failed' | 'filters/fetch' | 'filters/file-code' | 'filters/graphql' | 'filters/i-cursor' | 'filters/input' | 'filters/lcpt' | 'filters/link' | 'filters/location' | 'filters/memory-load' | 'filters/metadata' | 'filters/os' | 'filters/perfromance-network-request' | 'filters/platform' | 'filters/referrer' | 'filters/resize' | 'filters/rev-id' | 'filters/state-action' | 'filters/ttfb' | 'filters/user-alt' | 'filters/userid' | 'filters/view' | 'flag-na' | 'folder-plus' | 'folder2' | 'fullscreen' | 'funnel/cpu-fill' | 'funnel/cpu' | 'funnel/dizzy' | 'funnel/emoji-angry-fill' | 'funnel/emoji-angry' | 'funnel/emoji-dizzy-fill' | 'funnel/exclamation-circle-fill' | 'funnel/exclamation-circle' | 'funnel/file-earmark-break-fill' | 'funnel/file-earmark-break' | 'funnel/file-earmark-minus-fill' | 'funnel/file-earmark-minus' | 'funnel/file-medical-alt' | 'funnel/file-x' | 'funnel/hdd-fill' | 'funnel/hourglass-top' | 'funnel/image-fill' | 'funnel/image' | 'funnel/microchip' | 'funnel/mouse' | 'funnel/patch-exclamation-fill' | 'funnel/sd-card' | 'funnel-fill' | 'funnel-new' | 'funnel' | 'gear-fill' | 'geo-alt-fill-custom' | 'github' | 'graph-up-arrow' | 'graph-up' | 'grid-3x3' | 'grid-check' | 'grid-horizontal' | 'grip-horizontal' | 'hash' | 'hdd-stack' | 'headset' | 'heart-rate' | 'high-engagement' | 'history' | 'hourglass-start' | 'id-card' | 'image' | 'info-circle-fill' | 'info-circle' | 'info-square' | 'info' | 'inspect' | 'integrations/assist' | 'integrations/bugsnag-text' | 'integrations/bugsnag' | 'integrations/cloudwatch-text' | 'integrations/cloudwatch' | 'integrations/datadog' | 'integrations/elasticsearch-text' | 'integrations/elasticsearch' | 'integrations/github' | 'integrations/graphql' | 'integrations/jira-text' | 'integrations/jira' | 'integrations/mobx' | 'integrations/newrelic-text' | 'integrations/newrelic' | 'integrations/ngrx' | 'integrations/openreplay-text' | 'integrations/openreplay' | 'integrations/redux' | 'integrations/rollbar-text' | 'integrations/rollbar' | 'integrations/segment' | 'integrations/sentry-text' | 'integrations/sentry' | 'integrations/slack-bw' | 'integrations/slack' | 'integrations/stackdriver' | 'integrations/sumologic-text' | 'integrations/sumologic' | 'integrations/vuejs' | 'journal-code' | 'layer-group' | 'lightbulb-on' | 'lightbulb' | 'link-45deg' | 'list-alt' | 'list-ul' | 'list' | 'lock-alt' | 'map-marker-alt' | 'memory' | 'mic-mute' | 'mic' | 'minus' | 'mobile' | 'mouse-alt' | 'next1' | 'no-dashboard' | 'no-metrics-chart' | 'no-metrics' | 'os/android' | 'os/chrome_os' | 'os/fedora' | 'os/ios' | 'os/linux' | 'os/mac_os_x' | 'os/other' | 'os/ubuntu' | 'os/windows' | 'os' | 'pause-fill' | 'pause' | 'pdf-download' | 'pencil-stop' | 'pencil' | 'percent' | 'performance-icon' | 'person-fill' | 'person' | 'pie-chart-fill' | 'pin-fill' | 'play-circle-light' | 'play-circle' | 'play-fill-new' | 'play-fill' | 'play-hover' | 'play' | 'plus-circle' | 'plus' | 'prev1' | 'puzzle-piece' | 'puzzle' | 'question-circle' | 'question-lg' | 'quote-left' | 'quote-right' | 'redo-back' | 'redo' | 'remote-control' | 'replay-10' | 'resources-icon' | 'safe-fill' | 'safe' | 'sandglass' | 'search' | 'search_notification' | 'server' | 'share-alt' | 'shield-lock' | 'signup' | 'skip-forward-fill' | 'skip-forward' | 'slack' | 'slash-circle' | 'sliders' | 'social/slack' | 'social/trello' | 'spinner' | 'star-solid' | 'star' | 'step-forward' | 'stopwatch' | 'store' | 'sync-alt' | 'table-new' | 'table' | 'tablet-android' | 'tachometer-slow' | 'tachometer-slowest' | 'tags' | 'team-funnel' | 'telephone-fill' | 'telephone' | 'text-paragraph' | 'tools' | 'trash' | 'turtle' | 'user-alt' | 'user-circle' | 'user-friends' | 'users' | 'vendors/graphql' | 'vendors/mobx' | 'vendors/ngrx' | 'vendors/redux' | 'vendors/vuex' | 'web-vitals' | 'wifi' | 'window-alt' | 'window-restore' | 'window-x' | 'window' | 'zoom-in'; interface Props { name: IconNames; @@ -21,6 +21,7 @@ const SVG = (props: Props) => { case 'analytics': return ; case 'anchor': return ; case 'arrow-alt-square-right': return ; + case 'arrow-bar-left': return ; case 'arrow-clockwise': return ; case 'arrow-down': return ; case 'arrow-right-short': return ; @@ -69,6 +70,7 @@ const SVG = (props: Props) => { case 'bar-pencil': return ; case 'bell-fill': return ; case 'bell-plus': return ; + case 'bell-slash': return ; case 'bell': return ; case 'binoculars': return ; case 'book': return ; @@ -134,6 +136,7 @@ const SVG = (props: Props) => { case 'device': return ; case 'diagram-3': return ; case 'dizzy': return ; + case 'door-closed': return ; case 'doublecheck': return ; case 'download': return ; case 'drag': return ; @@ -199,6 +202,8 @@ const SVG = (props: Props) => { case 'filters/userid': return ; case 'filters/view': return ; case 'flag-na': return ; + case 'folder-plus': return ; + case 'folder2': return ; case 'fullscreen': return ; case 'funnel/cpu-fill': return ; case 'funnel/cpu': return ; @@ -329,6 +334,7 @@ const SVG = (props: Props) => { case 'plus': return ; case 'prev1': return ; case 'puzzle-piece': return ; + case 'puzzle': return ; case 'question-circle': return ; case 'question-lg': return ; case 'quote-left': return ; diff --git a/frontend/app/svg/icons/arrow-bar-left.svg b/frontend/app/svg/icons/arrow-bar-left.svg new file mode 100644 index 000000000..3558e407e --- /dev/null +++ b/frontend/app/svg/icons/arrow-bar-left.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/bell-slash.svg b/frontend/app/svg/icons/bell-slash.svg new file mode 100644 index 000000000..7b2a23785 --- /dev/null +++ b/frontend/app/svg/icons/bell-slash.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/door-closed.svg b/frontend/app/svg/icons/door-closed.svg new file mode 100644 index 000000000..5b9db0f13 --- /dev/null +++ b/frontend/app/svg/icons/door-closed.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/folder-plus.svg b/frontend/app/svg/icons/folder-plus.svg new file mode 100644 index 000000000..9e6d99b36 --- /dev/null +++ b/frontend/app/svg/icons/folder-plus.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/folder2.svg b/frontend/app/svg/icons/folder2.svg new file mode 100644 index 000000000..273f49c63 --- /dev/null +++ b/frontend/app/svg/icons/folder2.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/puzzle.svg b/frontend/app/svg/icons/puzzle.svg new file mode 100644 index 000000000..928b53546 --- /dev/null +++ b/frontend/app/svg/icons/puzzle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From a6227c72655dc45b009b01dbff78b16dd0fd084b Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 11 Oct 2022 10:36:20 +0200 Subject: [PATCH 07/15] change(ui) - dev tools --- .../app/components/Session_/Autoscroll.tsx | 6 +- .../Session_/BottomBlock/infoLine.module.css | 2 +- .../Session_/Console/ConsoleContent.js | 11 +- .../Session_/Console/console.module.css | 3 + .../Session_/Network/NetworkContent.js | 99 ++-- .../Session_/Performance/Performance.tsx | 436 +++++++++--------- .../Performance/performance.module.css | 1 + .../app/components/Session_/Player/Player.js | 4 +- .../Session_/TimeTable/timeTable.module.css | 6 +- .../DevTools/BottomBlock/BottomBlock.js | 18 + .../shared/DevTools/BottomBlock/Content.js | 17 + .../shared/DevTools/BottomBlock/Header.js | 26 ++ .../shared/DevTools/BottomBlock/InfoLine.js | 20 + .../BottomBlock/bottomBlock.module.css | 9 + .../DevTools/BottomBlock/content.module.css | 3 + .../DevTools/BottomBlock/header.module.css | 6 + .../shared/DevTools/BottomBlock/index.js | 8 + .../DevTools/BottomBlock/infoLine.module.css | 31 ++ .../shared/DevTools/BottomBlock/tabs.js | 9 + .../DevTools/NetworkPanel/NetworkPanel.tsx | 351 ++++++++++++++ .../shared/DevTools/TimeTable/BarRow.tsx | 96 ++++ .../shared/DevTools/TimeTable/TimeTable.tsx | 318 +++++++++++++ .../DevTools/TimeTable/barRow.module.css | 45 ++ .../shared/DevTools/TimeTable/index.js | 1 + .../DevTools/TimeTable/timeTable.module.css | 112 +++++ .../shared/DevTools/autoscroll.module.css | 12 + .../FetchDetailsModal/FetchDetailsModal.js | 64 ++- frontend/app/components/ui/Input/Input.tsx | 5 +- 28 files changed, 1417 insertions(+), 302 deletions(-) create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/BottomBlock.js create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/Content.js create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/Header.js create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/InfoLine.js create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/bottomBlock.module.css create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/content.module.css create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/header.module.css create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/index.js create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/infoLine.module.css create mode 100644 frontend/app/components/shared/DevTools/BottomBlock/tabs.js create mode 100644 frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx create mode 100644 frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx create mode 100644 frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx create mode 100644 frontend/app/components/shared/DevTools/TimeTable/barRow.module.css create mode 100644 frontend/app/components/shared/DevTools/TimeTable/index.js create mode 100644 frontend/app/components/shared/DevTools/TimeTable/timeTable.module.css create mode 100644 frontend/app/components/shared/DevTools/autoscroll.module.css diff --git a/frontend/app/components/Session_/Autoscroll.tsx b/frontend/app/components/Session_/Autoscroll.tsx index 051f2024f..ad2e82e01 100644 --- a/frontend/app/components/Session_/Autoscroll.tsx +++ b/frontend/app/components/Session_/Autoscroll.tsx @@ -112,15 +112,15 @@ export default class Autoscroll extends React.PureComponent -
    - {/* */} + {/*
    + {navigation && ( <> )} -
    +
    */}
    ); diff --git a/frontend/app/components/Session_/BottomBlock/infoLine.module.css b/frontend/app/components/Session_/BottomBlock/infoLine.module.css index b6798d1bf..37e47f013 100644 --- a/frontend/app/components/Session_/BottomBlock/infoLine.module.css +++ b/frontend/app/components/Session_/BottomBlock/infoLine.module.css @@ -6,7 +6,7 @@ display: flex; align-items: center; & >.infoPoint { - font-size: 12px; + font-size: 14px; display: flex; align-items: center; &:not(:last-child):after { diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js index 54a9745d0..0b56ead45 100644 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ b/frontend/app/components/Session_/Console/ConsoleContent.js @@ -83,11 +83,12 @@ export default class ConsoleContent extends React.PureComponent {
    @@ -101,7 +102,7 @@ export default class ConsoleContent extends React.PureComponent { {filtered.map((l, index) => (
    -
    + {/*
    {Duration.fromMillis(l.time).toFormat('mm:ss.SSS')} -
    -
    +
    */} +
    {renderWithNL(l.value)}
    diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css index 6f7079d94..2da78f540 100644 --- a/frontend/app/components/Session_/Console/console.module.css +++ b/frontend/app/components/Session_/Console/console.module.css @@ -15,6 +15,9 @@ display: flex; align-items: flex-start; border-bottom: solid thin $gray-light-shade; + &:hover { + background-coor: $active-blue !important; + } } .timestamp { diff --git a/frontend/app/components/Session_/Network/NetworkContent.js b/frontend/app/components/Session_/Network/NetworkContent.js index 082c87aa0..e0f91587c 100644 --- a/frontend/app/components/Session_/Network/NetworkContent.js +++ b/frontend/app/components/Session_/Network/NetworkContent.js @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; // import { connectPlayer } from 'Player'; -import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Button } from 'UI'; +import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; import { getRE } from 'App/utils'; import { TYPES } from 'Types/session/resource'; import { formatBytes } from 'App/utils'; @@ -48,22 +48,17 @@ export function renderType(r) { export function renderName(r) { return ( - {r.url}
    } - > -
    {r.name}
    - + {r.url}
    }> +
    {r.name}
    + ); } export function renderStart(r) { return (
    - - {Duration.fromMillis(r.time).toFormat('mm:ss.SSS')} - - -
    - ) + */} +
    + ); } const renderXHRText = () => ( @@ -243,39 +238,45 @@ export default class NetworkContent extends React.PureComponent { iconPosition="left" name="filter" onChange={this.onFilterChange} + height={28} /> - - - 0} - /> - 0} - /> - - - - +
    +
    + {}} label="4xx-5xx Only" /> +
    + + + 0} + /> + 0} + /> + + + + +
    @@ -296,11 +297,11 @@ export default class NetworkContent extends React.PureComponent { activeIndex={lastIndex} > {[ - { - label: 'Start', - width: 120, - render: renderStart, - }, + // { + // label: 'Start', + // width: 120, + // render: renderStart, + // }, { label: 'Status', dataKey: 'status', diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index 13c135f7b..994401141 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { connect } from 'react-redux'; import { Controls as PlayerControls, connectPlayer } from 'Player'; import { - AreaChart, + AreaChart, Area, ComposedChart, Line, - XAxis, + XAxis, YAxis, - Tooltip, + Tooltip, ResponsiveContainer, ReferenceLine, CartesianGrid, @@ -23,40 +23,35 @@ import stl from './performance.module.css'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; - const CPU_VISUAL_OFFSET = 10; - const FPS_COLOR = '#C5E5E7'; -const FPS_STROKE_COLOR = "#92C7CA"; -const FPS_LOW_COLOR = "pink"; -const FPS_VERY_LOW_COLOR = "red"; -const CPU_COLOR = "#A8D1DE"; -const CPU_STROKE_COLOR = "#69A5B8"; +const FPS_STROKE_COLOR = '#92C7CA'; +const FPS_LOW_COLOR = 'pink'; +const FPS_VERY_LOW_COLOR = 'red'; +const CPU_COLOR = '#A8D1DE'; +const CPU_STROKE_COLOR = '#69A5B8'; const USED_HEAP_COLOR = '#A9ABDC'; -const USED_HEAP_STROKE_COLOR = "#8588CF"; +const USED_HEAP_STROKE_COLOR = '#8588CF'; const TOTAL_HEAP_STROKE_COLOR = '#4A4EB7'; -const NODES_COUNT_COLOR = "#C6A9DC"; -const NODES_COUNT_STROKE_COLOR = "#7360AC"; -const HIDDEN_SCREEN_COLOR = "#CCC"; +const NODES_COUNT_COLOR = '#C6A9DC'; +const NODES_COUNT_STROKE_COLOR = '#7360AC'; +const HIDDEN_SCREEN_COLOR = '#CCC'; - -const CURSOR_COLOR = "#394EFF"; +const CURSOR_COLOR = '#394EFF'; const Gradient = ({ color, id }) => ( - - - + + + ); - -const TOTAL_HEAP = "Allocated Heap"; -const USED_HEAP = "JS Heap"; -const FPS = "Framerate"; -const CPU = "CPU Load"; -const NODES_COUNT = "Nodes Сount"; - +const TOTAL_HEAP = 'Allocated Heap'; +const USED_HEAP = 'JS Heap'; +const FPS = 'Framerate'; +const CPU = 'CPU Load'; +const NODES_COUNT = 'Nodes Сount'; const FPSTooltip = ({ active, payload }) => { if (!active || !payload || payload.length < 3) { @@ -64,8 +59,8 @@ const FPSTooltip = ({ active, payload }) => { } if (payload[0].value === null) { return ( -
    - {"Page is not active. User switched the tab or hid the window."} +
    + {'Page is not active. User switched the tab or hid the window.'}
    ); } @@ -79,9 +74,9 @@ const FPSTooltip = ({ active, payload }) => { } return ( -
    - {`${ FPS }: `} - { Math.trunc(payload[0].value) } +
    + {`${FPS}: `} + {Math.trunc(payload[0].value)}
    ); }; @@ -91,47 +86,47 @@ const CPUTooltip = ({ active, payload }) => { return null; } return ( -
    - {`${ CPU }: `} - { payload[0].value - CPU_VISUAL_OFFSET } - {"%"} +
    + {`${CPU}: `} + {payload[0].value - CPU_VISUAL_OFFSET} + {'%'}
    ); }; -const HeapTooltip = ({ active, payload}) => { +const HeapTooltip = ({ active, payload }) => { if (!active || payload.length < 2) return null; return ( -
    +

    - {`${ TOTAL_HEAP }: `} - { formatBytes(payload[0].value)} + {`${TOTAL_HEAP}: `} + {formatBytes(payload[0].value)}

    - {`${ USED_HEAP }: `} - { formatBytes(payload[1].value)} + {`${USED_HEAP}: `} + {formatBytes(payload[1].value)}

    ); -} +}; -const NodesCountTooltip = ({ active, payload} ) => { +const NodesCountTooltip = ({ active, payload }) => { if (!active || !payload || payload.length === 0) return null; return ( -
    +

    - {`${ NODES_COUNT }: `} - { payload[0].value } + {`${NODES_COUNT}: `} + {payload[0].value}

    ); -} +}; const TICKS_COUNT = 10; function generateTicks(data: Array): Array { if (data.length === 0) return []; const minTime = data[0].time; - const maxTime = data[data.length-1].time; + const maxTime = data[data.length - 1].time; const ticks = []; const tickGap = (maxTime - minTime) / (TICKS_COUNT + 1); @@ -159,8 +154,9 @@ function addFpsMetadata(data) { } else if (point.fps < LOW_FPS) { fpsLowMarker = LOW_FPS_MARKER_VALUE; } - } - if (point.fps == null || + } + if ( + point.fps == null || (i > 0 && data[i - 1].fps == null) //|| //(i < data.length-1 && data[i + 1].fps == null) ) { @@ -174,17 +170,17 @@ function addFpsMetadata(data) { fpsLowMarker, fpsVeryLowMarker, hiddenScreenMarker, - } + }; }); } -@connect(state => ({ - userDeviceHeapSize: state.getIn([ "sessions", "current", "userDeviceHeapSize" ]), - userDeviceMemorySize: state.getIn([ "sessions", "current", "userDeviceMemorySize" ]), +@connect((state) => ({ + userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']), + userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']), })) export default class Performance extends React.PureComponent { - _timeTicks = generateTicks(this.props.performanceChartData) - _data = addFpsMetadata(this.props.performanceChartData) + _timeTicks = generateTicks(this.props.performanceChartData); + _data = addFpsMetadata(this.props.performanceChartData); // state = { // totalHeap: false, // usedHeap: true, @@ -197,7 +193,7 @@ export default class Performance extends React.PureComponent { if (!!point) { PlayerControls.jump(point.time); } - } + }; onChartClick = (e) => { if (e === null) return; @@ -206,10 +202,10 @@ export default class Performance extends React.PureComponent { if (!!point) { PlayerControls.jump(point.time); } - } + }; render() { - const { + const { userDeviceHeapSize, userDeviceMemorySize, connType, @@ -218,19 +214,19 @@ export default class Performance extends React.PureComponent { avaliability = {}, } = this.props; const { fps, cpu, heap, nodes } = avaliability; - const avaliableCount = [ fps, cpu, heap, nodes ].reduce((c, av) => av ? c + 1 : c, 0); - const height = avaliableCount === 0 ? "0" : `${100 / avaliableCount}%`; + const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); + const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; return ( -
    - Performance +
    +
    Performance
    {/* */} = 1000 - ? `${ connBandwidth / 1000 } Mbps` - : `${ connBandwidth } Kbps` + value={ + connBandwidth >= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` } - display={ connBandwidth != null } + display={connBandwidth != null} />
    - { fps && - + {fps && ( + - + {/* */} {/* */} - - - + {/* */} - + style: { cursor: 'pointer' }, + }} + isAnimationActive={false} + /> - {/* */} - + - } - { cpu && - + )} + {cpu && ( + - + {/* */} - ""} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - - - - - - - - } - - { heap && - - - - - - {/* */} - ""} // tick={false} + this._timeTicks to cartesian array + tickFormatter={() => ''} domain={[0, 'dataMax']} ticks={this._timeTicks} > - + + + + + + + + )} + + {heap && ( + + + + + + {/* */} + ''} // tick={false} + this._timeTicks to cartesian array + domain={[0, 'dataMax']} + ticks={this._timeTicks} + > + max*1.2]} + domain={[0, (max) => max * 1.2]} /> - - + - } - { nodes && - + )} + {nodes && ( + - + {/* */} - ""} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - } + )} ); } } -export const ConnectedPerformance = connectPlayer(state => ({ +export const ConnectedPerformance = connectPlayer((state) => ({ performanceChartTime: state.performanceChartTime, performanceChartData: state.performanceChartData, connType: state.connType, diff --git a/frontend/app/components/Session_/Performance/performance.module.css b/frontend/app/components/Session_/Performance/performance.module.css index 4bd075eec..5c2b85578 100644 --- a/frontend/app/components/Session_/Performance/performance.module.css +++ b/frontend/app/components/Session_/Performance/performance.module.css @@ -3,4 +3,5 @@ padding: 2px 5px; border-radius: 3px; border: 1px solid #ccc; + color: $gray-dark !important; } \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index babe4f2b0..b0fcf583a 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -42,6 +42,7 @@ import Overlay from './Overlay'; import stl from './player.module.css'; import { updateLastPlayedSession } from 'Duck/sessions'; import OverviewPanel from '../OverviewPanel'; +import NetworkPanel from 'Shared/DevTools/NetworkPanel/NetworkPanel'; @connectPlayer(state => ({ live: state.live, @@ -112,7 +113,8 @@ export default class Player extends React.PureComponent { } { bottomBlock === NETWORK && - + // + } { bottomBlock === STACKEVENTS && diff --git a/frontend/app/components/Session_/TimeTable/timeTable.module.css b/frontend/app/components/Session_/TimeTable/timeTable.module.css index 17feaf459..c2412ff8d 100644 --- a/frontend/app/components/Session_/TimeTable/timeTable.module.css +++ b/frontend/app/components/Session_/TimeTable/timeTable.module.css @@ -9,7 +9,7 @@ $offset: 10px; .headers { box-shadow: 0 1px 2px 0 $gray-light; background-color: $gray-lightest; - color: $gray-medium; + color: $gray-darkest; font-size: 12px; overflow-x: hidden; white-space: nowrap; @@ -47,6 +47,10 @@ $offset: 10px; .row { display: flex; padding: 0 $offset; + + &:hover { + background-color: $active-blue; + } /*align-items: center; cursor: pointer; */ diff --git a/frontend/app/components/shared/DevTools/BottomBlock/BottomBlock.js b/frontend/app/components/shared/DevTools/BottomBlock/BottomBlock.js new file mode 100644 index 000000000..069757e60 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/BottomBlock.js @@ -0,0 +1,18 @@ +import React from 'react'; +import cn from 'classnames'; +import stl from './bottomBlock.module.css'; + +const BottomBlock = ({ + children = null, + className = '', + additionalHeight = 0, + ...props +}) => ( +
    + { children } +
    +); + +BottomBlock.displayName = 'BottomBlock'; + +export default BottomBlock; diff --git a/frontend/app/components/shared/DevTools/BottomBlock/Content.js b/frontend/app/components/shared/DevTools/BottomBlock/Content.js new file mode 100644 index 000000000..3df383911 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/Content.js @@ -0,0 +1,17 @@ +import React from 'react'; +import cn from 'classnames'; +import stl from './content.module.css'; + +const Content = ({ + children, + className, + ...props +}) => ( +
    + { children } +
    +); + +Content.displayName = 'Content'; + +export default Content; diff --git a/frontend/app/components/shared/DevTools/BottomBlock/Header.js b/frontend/app/components/shared/DevTools/BottomBlock/Header.js new file mode 100644 index 000000000..15dd7a0c9 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/Header.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import cn from 'classnames'; +import { closeBottomBlock } from 'Duck/components/player'; +import { Input, CloseButton } from 'UI'; +import stl from './header.module.css'; + +const Header = ({ + children, + className, + closeBottomBlock, + onFilterChange, + showClose = true, + ...props +}) => ( +
    +
    +
    { children }
    + { showClose && } +
    +
    +); + +Header.displayName = 'Header'; + +export default connect(null, { closeBottomBlock })(Header); diff --git a/frontend/app/components/shared/DevTools/BottomBlock/InfoLine.js b/frontend/app/components/shared/DevTools/BottomBlock/InfoLine.js new file mode 100644 index 000000000..3059c70d3 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/InfoLine.js @@ -0,0 +1,20 @@ +import React from 'react'; +import cn from 'classnames'; +import cls from './infoLine.module.css'; + +const InfoLine = ({ children }) => ( +
    + { children } +
    +) + +const Point = ({ label = '', value = '', display=true, color, dotColor }) => display + ?
    + { dotColor != null &&
    } + { `${label}` } { value } +
    + : null; + +InfoLine.Point = Point; + +export default InfoLine; diff --git a/frontend/app/components/shared/DevTools/BottomBlock/bottomBlock.module.css b/frontend/app/components/shared/DevTools/BottomBlock/bottomBlock.module.css new file mode 100644 index 000000000..99bdd42b4 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/bottomBlock.module.css @@ -0,0 +1,9 @@ + +.wrapper { + background: $white; + /* padding-right: 10px; */ + /* border: solid thin $gray-light; */ + height: 300px; + + border-top: thin dashed #cccccc; +} diff --git a/frontend/app/components/shared/DevTools/BottomBlock/content.module.css b/frontend/app/components/shared/DevTools/BottomBlock/content.module.css new file mode 100644 index 000000000..fe8303013 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/content.module.css @@ -0,0 +1,3 @@ +.content { + height: 86%; +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/BottomBlock/header.module.css b/frontend/app/components/shared/DevTools/BottomBlock/header.module.css new file mode 100644 index 000000000..99faa61c7 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/header.module.css @@ -0,0 +1,6 @@ + +.header { + padding: 0 10px; + height: 40px; + border-bottom: 1px solid $gray-light; +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/BottomBlock/index.js b/frontend/app/components/shared/DevTools/BottomBlock/index.js new file mode 100644 index 000000000..846d7ec6f --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/index.js @@ -0,0 +1,8 @@ +import BottomBlock from './BottomBlock'; +import Header from './Header'; +import Content from './Content'; + +BottomBlock.Header = Header; +BottomBlock.Content = Content; + +export default BottomBlock; \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/BottomBlock/infoLine.module.css b/frontend/app/components/shared/DevTools/BottomBlock/infoLine.module.css new file mode 100644 index 000000000..37e47f013 --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/infoLine.module.css @@ -0,0 +1,31 @@ + + +.info { + padding-left: 10px; + height: 36px; + display: flex; + align-items: center; + & >.infoPoint { + font-size: 14px; + display: flex; + align-items: center; + &:not(:last-child):after { + content: ''; + margin: 0 12px; + height: 30px; + border-right: 1px solid $gray-light-shade; + } + & .label { + font-weight: 500; + margin-right: 6px; + } + } +} + +.dot { + width: 10px; + height: 10px; + border-radius: 5px; + margin-right: 5px; +} + diff --git a/frontend/app/components/shared/DevTools/BottomBlock/tabs.js b/frontend/app/components/shared/DevTools/BottomBlock/tabs.js new file mode 100644 index 000000000..6addd161e --- /dev/null +++ b/frontend/app/components/shared/DevTools/BottomBlock/tabs.js @@ -0,0 +1,9 @@ +// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player'; +// +// +// export default { +// [NONE]: { +// Component: null, +// +// } +// } \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx new file mode 100644 index 000000000..0b8b3a39c --- /dev/null +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -0,0 +1,351 @@ +import React, { useState } from 'react'; +import cn from 'classnames'; +// import { connectPlayer } from 'Player'; +import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI'; +import { getRE } from 'App/utils'; +import { TYPES } from 'Types/session/resource'; +import { formatBytes } from 'App/utils'; +import { formatMs } from 'App/date'; + +import TimeTable from '../TimeTable'; +import BottomBlock from '../BottomBlock'; +import InfoLine from '../BottomBlock/InfoLine'; +// import stl from './network.module.css'; +import { Duration } from 'luxon'; +import { connectPlayer, jump, pause } from 'Player'; +import { useModal } from 'App/components/Modal'; +import FetchDetailsModal from 'Shared/FetchDetailsModal'; + +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: any = { + [XHR]: TYPES.XHR, + [JS]: TYPES.JS, + [CSS]: TYPES.CSS, + [IMG]: TYPES.IMG, + [MEDIA]: TYPES.MEDIA, + [OTHER]: TYPES.OTHER, +}; +const TABS: any = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ + text: tab, + key: tab, +})); + +const DOM_LOADED_TIME_COLOR = 'teal'; +const LOAD_TIME_COLOR = 'red'; + +export function renderType(r) { + return ( + {r.type}
    }> +
    {r.type}
    + + ); +} + +export function renderName(r: any) { + return ( + {r.url}
    }> +
    {r.name}
    + + ); +} + +export function renderStart(r: any) { + return ( +
    + {Duration.fromMillis(r.time).toFormat('mm:ss.SSS')} +
    + ); +} + +const renderXHRText = () => ( + + {XHR} + + Use our{' '} + + Fetch plugin + + {' to capture HTTP requests and responses, including status codes and bodies.'}
    + We also provide{' '} + + support for GraphQL + + {' for easy debugging of your queries.'} + + } + className="ml-1" + /> +
    +); + +function renderSize(r: any) { + if (r.responseBodySize) return formatBytes(r.responseBodySize); + let triggerText; + let content; + if (r.decodedBodySize == null) { + triggerText = 'x'; + content = 'Not captured'; + } else { + const headerSize = r.headerSize || 0; + const encodedSize = r.encodedBodySize || 0; + const transferred = headerSize + encodedSize; + const showTransferred = r.headerSize != null; + + triggerText = formatBytes(r.decodedBodySize); + content = ( +
      + {showTransferred && ( +
    • {`${formatBytes(r.encodedBodySize + headerSize)} transfered over network`}
    • + )} +
    • {`Resource size: ${formatBytes(r.decodedBodySize)} `}
    • +
    + ); + } + + return ( + +
    {triggerText}
    +
    + ); +} + +export function renderDuration(r: any) { + if (!r.success) return 'x'; + + const text = `${Math.floor(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}
    +
    + ); +} + +interface Props { + location: any; + resources: any; + domContentLoadedTime: any; + loadTime: any; + playing: boolean; + domBuildingTime: any; + fetchPresented: boolean; + listNow: any; + + currentIndex: any; + time: any; +} +function NetworkPanel(props: Props) { + const { + resources, + time, + currentIndex, + domContentLoadedTime, + loadTime, + playing, + domBuildingTime, + fetchPresented, + listNow, + } = props; + const { showModal, hideModal } = useModal(); + const [activeTab, setActiveTab] = useState(ALL); + const [filter, setFilter] = useState(''); + const [showOnlyErrors, setShowOnlyErrors] = useState(false); + const onTabClick = (activeTab: any) => setActiveTab(activeTab); + const onFilterChange = ({ target: { value } }: any) => setFilter(value); + const additionalHeight = 0; + + const resourcesSize = resources.reduce( + (sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0), + 0 + ); + + const transferredSize = resources.reduce( + (sum: any, { headerSize, encodedBodySize }: any) => + sum + (headerSize || 0) + (encodedBodySize || 0), + 0 + ); + + const filterRE = getRE(filter, 'i'); + let filtered = resources.filter( + ({ type, name }: any) => + filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) + ); + const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1; + const referenceLines = []; + if (domContentLoadedTime != null) { + referenceLines.push({ + time: domContentLoadedTime.time, + color: DOM_LOADED_TIME_COLOR, + }); + } + if (loadTime != null) { + referenceLines.push({ + time: loadTime.time, + color: LOAD_TIME_COLOR, + }); + } + + const onRowClick = (row: any) => { + showModal(, { right: true }) + } + + return ( + + + +
    + Network + +
    + +
    + +
    +
    + setShowOnlyErrors(!showOnlyErrors)} label="4xx-5xx Only" /> +
    + + + 0} + /> + 0} + /> + + + + +
    + + + No Data +
    + } + size="small" + show={filtered.length === 0} + > + + {[ + // { + // label: 'Start', + // width: 120, + // render: renderStart, + // }, + { + label: 'Status', + dataKey: 'status', + width: 70, + }, + { + label: 'Type', + dataKey: 'type', + width: 90, + render: renderType, + }, + { + label: 'Name', + width: 240, + render: renderName, + }, + { + label: 'Size', + width: 60, + render: renderSize, + }, + { + label: 'Time', + width: 80, + render: renderDuration, + }, + ]} + + + + + + ); +} + +export default connectPlayer((state: any) => ({ + 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, +}))(NetworkPanel); diff --git a/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx new file mode 100644 index 000000000..9de1a8279 --- /dev/null +++ b/frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx @@ -0,0 +1,96 @@ +import { Popup } from 'UI'; +import { percentOf } from 'App/utils'; +import styles from './barRow.module.css' +import tableStyles from './timeTable.module.css'; +import React from 'react'; + +const formatTime = time => time < 1000 ? `${time.toFixed(2)}ms` : `${time / 1000}s`; + +interface Props { + resource: { + time: number + ttfb?: number + duration?: number + key: string + } + popup?: boolean + timestart: number + timewidth: number +} + +// TODO: If request has no duration, set duration to 0.2s. Enforce existence of duration in the future. +const BarRow = ({ resource: { time, ttfb = 0, duration = 200, key }, popup = false, timestart = 0, timewidth }: Props) => { + const timeOffset = time - timestart; + ttfb = ttfb || 0; + const trigger = ( +
    +
    +
    +
    + ); + if (!popup) return
    {trigger}
    ; + + return ( +
    + + {ttfb != null && +
    +
    {'Waiting (TTFB)'}
    +
    +
    +
    +
    {formatTime(ttfb)}
    +
    + } +
    +
    {'Content Download'}
    +
    +
    +
    +
    {formatTime(duration - ttfb)}
    +
    + + } + size="mini" + position="top center" + /> +
    + ); +} + +BarRow.displayName = "BarRow"; + +export default BarRow; \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx new file mode 100644 index 000000000..c6e3ea716 --- /dev/null +++ b/frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx @@ -0,0 +1,318 @@ +import React from 'react'; +import { List, AutoSizer } from 'react-virtualized'; +import cn from 'classnames'; +import { Duration } from "luxon"; +import { NoContent, IconButton, Button } from 'UI'; +import { percentOf } from 'App/utils'; + +import BarRow from './BarRow'; +import stl from './timeTable.module.css'; + +import autoscrollStl from '../autoscroll.module.css'; //aaa + +type Timed = { + time: number; +}; + +type Durationed = { + duration: number; +}; + +type CanBeRed = { + //+isRed: boolean, + isRed: () => boolean; +}; + +interface Row extends Timed, Durationed, CanBeRed { + [key: string]: any, key: string +} + +type Line = { + color: string; // Maybe use typescript? + hint?: string; + onClick?: any; +} & Timed; + +type Column = { + label: string; + width: number; + dataKey?: string; + render?: (row: any) => void + referenceLines?: Array; + style?: React.CSSProperties; +} & RenderOrKey; + +// type RenderOrKey = { // Disjoint? +// render: Row => React.Node +// } | { +// dataKey: string, +// } +type RenderOrKey = + | { + render?: (row: Row) => React.ReactNode; + key?: string; + } + | { + dataKey: string; + }; + +type Props = { + className?: string; + rows: Array; + children: Array; + tableHeight?: number + activeIndex?: number + renderPopup?: boolean + navigation?: boolean + referenceLines?: any[] + additionalHeight?: number + hoverable?: boolean + onRowClick?: (row: any, index: number) => void +}; + +type TimeLineInfo = { + timestart: number; + timewidth: number; +}; + +type State = TimeLineInfo & typeof initialState; + +//const TABLE_HEIGHT = 195; +let _additionalHeight = 0; +const ROW_HEIGHT = 32; +//const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT); + +const TIME_SECTIONS_COUNT = 8; +const ZERO_TIMEWIDTH = 1000; +function formatTime(ms: number) { + if (ms < 0) return ''; + if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS') + return Duration.fromMillis(ms).toFormat('mm:ss'); +} + +function computeTimeLine(rows: Array, firstVisibleRowIndex: number, visibleCount: number): TimeLineInfo { + const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight); + let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0; + // TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request + const timeend = visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) : 0; + let timewidth = timeend - timestart; + const offset = timewidth / 70; + if (timestart >= offset) { + timestart -= offset; + } + timewidth *= 1.5; // += offset; + if (timewidth === 0) { + timewidth = ZERO_TIMEWIDTH; + } + return { + timestart, + timewidth, + }; +} + +const initialState = { + firstVisibleRowIndex: 0, +}; + +export default class TimeTable extends React.PureComponent { + state = { + ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), + ...initialState, + }; + + get tableHeight() { + return this.props.tableHeight || 195; + } + + get visibleCount() { + return Math.ceil(this.tableHeight / ROW_HEIGHT); + } + + scroller = React.createRef(); + autoScroll = true; + + componentDidMount() { + if (this.scroller.current) { + this.scroller.current.scrollToRow(this.props.activeIndex); + } + } + + componentDidUpdate(prevProps: any, prevState: any) { + if ( + prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex || + (this.props.rows.length <= this.visibleCount + _additionalHeight && prevProps.rows.length !== this.props.rows.length) + ) { + this.setState({ + ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), + }); + } + if (this.props.activeIndex && this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current) { + this.scroller.current.scrollToRow(this.props.activeIndex); + } + } + + onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number; scrollHeight: number; clientHeight: number }): void => { + const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); + + if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { + this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2; + this.setState({ firstVisibleRowIndex }); + } + }; + + renderRow = ({ index, key, style: rowStyle }: any) => { + const { activeIndex } = this.props; + const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props; + const { timestart, timewidth } = this.state; + const row = rows[index]; + return ( +
    activeIndex, + })} + onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined} + id="table-row" + > + {columns.map(({ dataKey, render, width }) => ( +
    + {render ? render(row) : row[dataKey || ''] || {'empty'}} +
    + ))} +
    + +
    +
    + ); + }; + + onPrevClick = () => { + let prevRedIndex = -1; + for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) { + if (this.props.rows[i].isRed()) { + prevRedIndex = i; + break; + } + } + if (this.scroller.current != null) { + this.scroller.current.scrollToRow(prevRedIndex); + } + }; + + onNextClick = () => { + let prevRedIndex = -1; + for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) { + if (this.props.rows[i].isRed()) { + prevRedIndex = i; + break; + } + } + if (this.scroller.current != null) { + this.scroller.current.scrollToRow(prevRedIndex); + } + }; + + render() { + const { className, rows, children: columns, navigation = false, referenceLines = [], additionalHeight = 0, activeIndex } = this.props; + const { timewidth, timestart } = this.state; + + _additionalHeight = additionalHeight; + + const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); + const timeColumns: number[] = []; + if (timewidth > 0) { + for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { + timeColumns.push(timestart + i * sectionDuration); + } + } + + const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth); + + const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); + + return ( +
    + {navigation && ( +
    +
    + )} +
    +
    + {columns.map(({ label, width }) => ( +
    + {label} +
    + ))} +
    +
    + {timeColumns.map((time, i) => ( +
    + {formatTime(time)} +
    + ))} +
    +
    + + +
    +
    + {timeColumns.map((_, index) => ( +
    + ))} + {visibleRefLines.map(({ time, color, onClick }) => ( +
    + ))} +
    + + {({ width }: { width: number }) => ( + + )} + +
    + +
    + ); + } +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TimeTable/barRow.module.css b/frontend/app/components/shared/DevTools/TimeTable/barRow.module.css new file mode 100644 index 000000000..e45d1f7b2 --- /dev/null +++ b/frontend/app/components/shared/DevTools/TimeTable/barRow.module.css @@ -0,0 +1,45 @@ + + + +.barWrapper { + display: flex; + position: absolute; + top: 35%; + bottom: 35%; + border-radius: 3px; + overflow: hidden; +} + +.downloadBar, .ttfbBar { + /* box-shadow: inset 0px 0px 0px 1px $teal; */ + height: 100%; + box-sizing: border-box; + position: relative; +} +.ttfbBar { + background-color: rgba(175, 226, 221, 0.8); +} +.downloadBar { + background-color: rgba(133, 200, 192, 0.8); +} + +.popupRow { + color: $gray-medium; + display: flex; + align-items: center; + padding: 2px 0; + font-size: 12px; +} +.title { + width: 105px; +} +.time { + width: 60px; + padding-left: 10px; +} +.popupBarWrapper { + width: 220px; + height: 15px; + border-radius: 3px; + overflow: hidden; +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TimeTable/index.js b/frontend/app/components/shared/DevTools/TimeTable/index.js new file mode 100644 index 000000000..c3c329b0a --- /dev/null +++ b/frontend/app/components/shared/DevTools/TimeTable/index.js @@ -0,0 +1 @@ +export { default } from './TimeTable'; \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/TimeTable/timeTable.module.css b/frontend/app/components/shared/DevTools/TimeTable/timeTable.module.css new file mode 100644 index 000000000..c2412ff8d --- /dev/null +++ b/frontend/app/components/shared/DevTools/TimeTable/timeTable.module.css @@ -0,0 +1,112 @@ + + +$offset: 10px; + +.timeCell { + border-left: solid thin rgba(0, 0, 0, 0.05); +} + +.headers { + box-shadow: 0 1px 2px 0 $gray-light; + background-color: $gray-lightest; + color: $gray-darkest; + font-size: 12px; + overflow-x: hidden; + white-space: nowrap; + width: 100%; + display: flex; + padding: 0 $offset; +} +.infoHeaders { + text-transform: uppercase; + display: flex; + & .headerCell { + padding: 4px 2px; + } +} +.waterfallHeaders { + display: flex; + flex: 1; + & .timeCell { + flex: 1; + overflow: hidden; + padding: 4px 0; + } +} + +.list { + /* TODO hide the scrollbar track */ + &::-webkit-scrollbar { + width: 1px; + } + scrollbar-width: thin; + font-size: 12px; + font-family: 'Menlo', 'monaco', 'consolas', monospace; +} + +.row { + display: flex; + padding: 0 $offset; + + &:hover { + background-color: $active-blue; + } + /*align-items: center; + cursor: pointer; + */ + /* &:nth-child(even) { + background-color: $gray-lightest; + } */ + /* & > div:first-child { + padding-left: 5px; + }*/ +} +.cell { + height: 100%; + display: flex; + align-items: center; + overflow: hidden; + padding: 0 2px; +} +.hoverable { + transition: all 0.3s; + cursor: pointer; + &:hover { + background-color: $active-blue; + transition: all 0.2s; + color: $gray-dark; + } +} +.timeBarWrapper{ + overflow: hidden; +} + +.timePart { + position: absolute; + top: 0; + bottom: 0; + /*left:0;*/ + right: 0; + display: flex; + margin: 0 $offset; + & .timeCell { + height: 100%; + flex: 1; + z-index: 1; + pointer-events: none; + } + & .refLine { + position: absolute; + height: 100%; + width: 1px; + z-index: 1; + } +} + +.activeRow { + background-color: $teal-light; +} + +.inactiveRow { + opacity: 0.5; +} \ No newline at end of file diff --git a/frontend/app/components/shared/DevTools/autoscroll.module.css b/frontend/app/components/shared/DevTools/autoscroll.module.css new file mode 100644 index 000000000..209badfb2 --- /dev/null +++ b/frontend/app/components/shared/DevTools/autoscroll.module.css @@ -0,0 +1,12 @@ +.navButtons { + position: absolute; + + background: rgba(255, 255, 255, 0.5); + padding: 4px; + + right: 24px; + top: 8px; + z-index: 1; +} + + diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js index 6f3ad549e..ef29ff128 100644 --- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js +++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js @@ -128,24 +128,60 @@ export default class FetchDetailsModal extends React.PureComponent { } render() { - const { - resource: { method, url, duration }, - nextClick, - prevClick, - first = false, - last = false, - } = this.props; + const { resource, nextClick, prevClick, first = false, last = false } = this.props; + const { method, url, duration } = resource; const { activeTab, tabs } = this.state; - - const _duration = parseInt(duration) - console.log('_duration', _duration); + const _duration = parseInt(duration); + console.log('_duration', resource); return (
    -
    {'URL'}
    -
    {url}
    +
    Network Request
    +
    +
    Name
    +
    + {resource.name} +
    +
    + +
    +
    Type
    +
    + {resource.type} +
    +
    + + {method && ( +
    +
    Request Method
    +
    + {resource.method} +
    +
    + )} + + {resource.status && ( +
    +
    Status
    +
    + {resource.status === '200' &&
    } + {resource.status} +
    +
    + )} + + {!!_duration && ( +
    +
    Time
    +
    + {_duration} ms +
    +
    + )} + + {/*
    {url}
    */}
    - {method && ( + {/* {method && (
    Method
    {method}
    @@ -156,7 +192,7 @@ export default class FetchDetailsModal extends React.PureComponent {
    Duration
    {_duration } ms
    - )} + )} */}
    diff --git a/frontend/app/components/ui/Input/Input.tsx b/frontend/app/components/ui/Input/Input.tsx index 1c36f7a8a..b90a499f3 100644 --- a/frontend/app/components/ui/Input/Input.tsx +++ b/frontend/app/components/ui/Input/Input.tsx @@ -9,10 +9,11 @@ interface Props { leadingButton?: React.ReactNode; type?: string; rows?: number; + height?: number; [x: string]: any; } const Input = React.forwardRef((props: Props, ref: any) => { - const { className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props; + const { height = 36, className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props; return (
    {icon && } @@ -29,7 +30,7 @@ const Input = React.forwardRef((props: Props, ref: any) => { From c2e295e73839fe39ff07d233c8340dc25ee02142 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 11 Oct 2022 14:02:32 +0200 Subject: [PATCH 08/15] change(ui) - merge fetch calls with network --- .../DevTools/NetworkPanel/NetworkPanel.tsx | 54 ++++++++++++----- .../FetchDetailsModal/FetchDetailsModal.js | 59 +++++++++++-------- frontend/app/components/ui/Input/Input.tsx | 5 +- 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 0b8b3a39c..3ba6041ea 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -40,6 +40,12 @@ const TABS: any = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({ const DOM_LOADED_TIME_COLOR = 'teal'; const LOAD_TIME_COLOR = 'red'; +function compare(a: any, b: any, key: string) { + if (a[key] > b[key]) return 1; + if (a[key] < b[key]) return -1; + return 0; +} + export function renderType(r) { return ( {r.type}
    }> @@ -153,13 +159,11 @@ export function renderDuration(r: any) { interface Props { location: any; resources: any; + fetchList: any; domContentLoadedTime: any; loadTime: any; playing: boolean; domBuildingTime: any; - fetchPresented: boolean; - listNow: any; - currentIndex: any; time: any; } @@ -172,16 +176,17 @@ function NetworkPanel(props: Props) { loadTime, playing, domBuildingTime, - fetchPresented, - listNow, + fetchList, } = props; const { showModal, hideModal } = useModal(); const [activeTab, setActiveTab] = useState(ALL); + const [sortBy, setSortBy] = useState('time'); const [filter, setFilter] = useState(''); const [showOnlyErrors, setShowOnlyErrors] = useState(false); const onTabClick = (activeTab: any) => setActiveTab(activeTab); const onFilterChange = ({ target: { value } }: any) => setFilter(value); const additionalHeight = 0; + const fetchPresented = fetchList.length > 0; const resourcesSize = resources.reduce( (sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0), @@ -195,11 +200,23 @@ function NetworkPanel(props: Props) { ); const filterRE = getRE(filter, 'i'); - let filtered = resources.filter( - ({ type, name }: any) => - filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) + let filtered = resources; + fetchList.forEach( + (fetchCall: any) => filtered = filtered.filter((networkCall: any) => networkCall.url !== fetchCall.url) ); - const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1; + filtered = filtered.concat(fetchList); + filtered = filtered.sort((a: any, b: any) => { + return compare(a, b, sortBy); + }); + + filtered = filtered.filter( + ({ type, name, status }: any) => + (!!filter ? filterRE.test(status) || filterRE.test(name) || filterRE.test(type) : true) && + (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab]) && + (showOnlyErrors ? parseInt(status) >= 400 : true) + ); + + // const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1; const referenceLines = []; if (domContentLoadedTime != null) { referenceLines.push({ @@ -215,8 +232,8 @@ function NetworkPanel(props: Props) { } const onRowClick = (row: any) => { - showModal(, { right: true }) - } + showModal(, { right: true }); + }; return ( @@ -234,18 +251,24 @@ function NetworkPanel(props: Props) {
    - setShowOnlyErrors(!showOnlyErrors)} label="4xx-5xx Only" /> + setShowOnlyErrors(!showOnlyErrors)} + label="4xx-5xx Only" + />
    @@ -295,7 +318,7 @@ function NetworkPanel(props: Props) { // navigation onRowClick={onRowClick} additionalHeight={additionalHeight} - activeIndex={lastIndex} + // activeIndex={lastIndex} > {[ // { @@ -341,11 +364,10 @@ function NetworkPanel(props: Props) { export default connectPlayer((state: any) => ({ location: state.location, resources: state.resourceList, + fetchList: state.fetchList, domContentLoadedTime: state.domContentLoadedTime, loadTime: state.loadTime, // time: state.time, playing: state.playing, domBuildingTime: state.domBuildingTime, - fetchPresented: state.fetchList.length > 0, - listNow: state.resourceListNow, }))(NetworkPanel); diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js index ef29ff128..1f10a373c 100644 --- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js +++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.js @@ -1,9 +1,10 @@ import React from 'react'; -import { JSONTree, NoContent, Button, Tabs } from 'UI'; +import { JSONTree, NoContent, Button, Tabs, Icon } from 'UI'; import cn from 'classnames'; import stl from './fetchDetails.module.css'; import Headers from './components/Headers'; import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { TYPES } from 'Types/session/resource'; const HEADERS = 'HEADERS'; const REQUEST = 'REQUEST'; @@ -128,11 +129,10 @@ export default class FetchDetailsModal extends React.PureComponent { } render() { - const { resource, nextClick, prevClick, first = false, last = false } = this.props; + const { resource, fetchPresented, nextClick, prevClick, first = false, last = false } = this.props; const { method, url, duration } = resource; const { activeTab, tabs } = this.state; const _duration = parseInt(duration); - console.log('_duration', resource); return (
    @@ -164,7 +164,9 @@ export default class FetchDetailsModal extends React.PureComponent {
    Status
    - {resource.status === '200' &&
    } + {resource.status === '200' && ( +
    + )} {resource.status}
    @@ -179,29 +181,40 @@ export default class FetchDetailsModal extends React.PureComponent {
    )} - {/*
    {url}
    */} -
    - {/* {method && ( -
    -
    Method
    -
    {method}
    + {resource.type === TYPES.XHR && ( +
    +
    + + Get more out of network requests
    - )} - {!!_duration && ( -
    -
    Duration
    -
    {_duration } ms
    -
    - )} */} -
    +
      +
    • + Integrate{' '} + + Fetch plugin + {' '} + to capture fetch payloads. +
    • +
    • + Find a detailed{' '} + + video tutorial + {' '} + to understand practical example of how to use fetch plugin. +
    • +
    +
    + )}
    -
    - -
    - {this.renderActiveTab(activeTab)} + {resource.type === TYPES.FETCH && ( +
    + +
    + {this.renderActiveTab(activeTab)} +
    -
    + )} {/*