Skip to content

Commit b7b9a1b

Browse files
authored
Merge pull request #251 from oslabs-beta/master
Reactime X Updates
2 parents ac10341 + 7783a64 commit b7b9a1b

File tree

22 files changed

+27111
-278
lines changed

22 files changed

+27111
-278
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@
4343
<a href="#how-to-use">How To Use</a> • <a href="#features">Features</a> • <a href="https://reactime.io">Website</a> • <a href="#read-more">Read More</a>
4444
</p>
4545

46-
Currently, Reactime supports React apps using stateful components and Hooks, including frameworks like Gatsby and Next.js, with beta support for Recoil and Context API.
46+
Currently, Reactime supports React apps using stateful components and Hooks, with beta support for Recoil and Context API and frameworks like Gatsby and Next.js.
4747

48-
<b>Reactime version 9.0</b> allows you to run A/B testing on your application by storing a "series" of state data snapshots. At any stage in the dev cycle, devs could run Reactime again and select any past series to do an A/B test with the current series of snapshots. With Save Series, developers have access to view trends in their App's component render times during development by comparing the previous series of snapshots.
48+
<b>Reactime version 11.0</b> implements full compatibility with React Hooks. Additionally, hover functionality was added to all of the nodes that populate in the history tab, allowing developers to more easily view the state at that snapshot.
4949

50-
Reactime 9.0 fixes previous version bugs and incorporates improved user experience for saved snapshot series.
50+
Reactime 11.0 fixes existing bugs while also improving the user experience for information tooltips.
5151

5252
After installing Reactime, you can test its functionalities with your React application in development mode.
5353

@@ -152,6 +152,8 @@ After cloning this repository, developers can simply run `npm run docs` at the r
152152
- [React Fiber and Reactime](https://medium.com/@aquinojardim/react-fiber-reactime-4-0-f200f02e7fa8)
153153
- [Meet Reactime - a time-traveling State Debugger for React](https://medium.com/@yujinkay/meet-reactime-a-time-traveling-state-debugger-for-react-24f0fce96802)
154154
- [Deep in Weeds with Reactime, Concurrent React_fiberRoot, and Browser History Caching](https://itnext.io/deep-in-the-weeds-with-reactime-concurrent-react-fiberroot-and-browser-history-caching-7ce9d7300abb)
155+
- [Time-Traveling Through React State with Reactime 9.0](https://rxlina.medium.com/time-traveling-through-react-state-with-reactime-9-0-371dbdc99319)
156+
- [What time is it? Reactime!](https://medium.com/@liuedar/what-time-is-it-reactime-fd7267b9eb89)
155157

156158
## <b>Authors</b>
157159
- **Harry Fox** - [@StackOverFlowWhereArtThou](https://github.com/StackOverFlowWhereArtThou)
@@ -201,6 +203,11 @@ After cloning this repository, developers can simply run `npm run docs` at the r
201203
- **Andy Tsou** - [@andytsou19](https://github.com/andytsou19)
202204
- **Feiyi Wu** - [@FreyaWu](https://github.com/FreyaWu)
203205
- **Viet Nguyen** - [@vnguyen95](https://github.com/vnguyen95)
206+
- **Alex Gomez** - [@alexgomez9](https://github.com/alexgomez9)
207+
- **Edar Liu** - [@liuedar](https://github.com/liuedar)
208+
- **Kristina Wallen** - [@kristinawallen](https://github.com/kristinawallen)
209+
- **Quan Le** - [@blachfog](https://github.com/Blachfog)
210+
- **Robert Maeda** - [@robmaeda](https://github.com/robmaeda)
204211

205212
## <b>License </b>
206213

src/app/components/Action.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import React from 'react';
66
import ReactHover, { Trigger, Hover } from 'react-hover';
77
import { changeView, changeSlider } from '../actions/actions';
8+
import snapshots from './snapshots';
89

910
/**
1011
* @template ActionProps Props for the action component
@@ -148,8 +149,8 @@ const Action = (props: ActionProps): JSX.Element => {
148149
</div>
149150
</Trigger>
150151
<Hover type="hover">
151-
<div style={{ padding: '0.5rem 1rem' }} id="hover-box">
152-
<p>{logChangedState(index)}</p>
152+
<div style={{ zIndex: 1, position: 'relative', padding: '0.5rem 1rem' }} id="hover-box">
153+
<p>{(logChangedState(index))}</p>
153154
</div>
154155
</Hover>
155156
</ReactHover>

src/app/components/ComponentMap.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,9 @@ export default function ComponentMap({
236236

237237
<svg ref={containerRef} width={totalWidth} height={totalHeight}>
238238
<LinearGradient id="links-gradient" from="#fd9b93" to="#fe6e9e" />
239-
<rect width={totalWidth} height={totalHeight} rx={14} fill="#242529" />
239+
<rect onClick={() => {
240+
setTooltip(false);
241+
hideTooltip();}} width={totalWidth} height={totalHeight} rx={14} fill="#242529" />
240242
<Group top={margin.top} left={margin.left}>
241243
<Tree
242244
root={hierarchy(startNode || data, d => (d.isExpanded ? null : d.children))}

src/app/components/History.tsx

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
/* eslint-disable react-hooks/exhaustive-deps */
22
// @ts-nocheck
33
import React, { Component, useEffect, useState } from 'react';
4+
// ReactHover does not refer to individual nodes within the history tab but rather can only wrap the entire SVG div
5+
import ReactHover, { Trigger, Hover } from 'react-hover';
6+
// used in action.tsx to format the return data of findDiff into a react component, not needed since the return of findDiff in History.tsx is simple a divs html
7+
import ReactHtmlParser from 'react-html-parser';
8+
// formatting findDiff return data to show the changes with green and red background colors, aligns with actions.tsx
9+
import { diff, formatters } from 'jsondiffpatch';
410
import * as d3 from 'd3';
11+
// legend key taken out before ReactimeX(v 10.0)
512
import LegendKey from './legend';
613
import { changeView, changeSlider } from '../actions/actions';
714

15+
//unused function definition, didnt break reactime when commented out
816
const filterHooks = (data: any[]) => {
917
if (data[0].children && data[0].state === 'stateless') {
1018
return filterHooks(data[0].children);
@@ -26,6 +34,8 @@ function History(props: Record<string, unknown>) {
2634
hierarchy,
2735
dispatch,
2836
currLocation,
37+
//snapshots array passed down from state route, needed for findDiff function
38+
snapshots,
2939
} = props;
3040

3141
const svgRef = React.useRef(null);
@@ -102,9 +112,25 @@ function History(props: Record<string, unknown>) {
102112
dispatch(changeView(d.data.index));
103113
dispatch(changeSlider(d.data.index));
104114
})
115+
//added to display state change information to node tree
116+
.on('mouseover', d => {
117+
// created popup div and appended it to display div(returned in this function)
118+
// D3 doesn't utilize z-index for priority, rather decides on placement by order of rendering
119+
// needed to define the return div with a className to have a target to append to with the correct level of priority
120+
const div = d3.select('.display').append('div')
121+
.attr('class', 'tooltip')
122+
.style('opacity', 1)
123+
.style('left', (d3.event.pageX) + 'px')
124+
.style('top', (d3.event.pageY) + 'px')
125+
d3.selectAll('.tooltip').html(findDiff(d.data.index));
126+
})
127+
.on('mouseout', d => {
128+
// when appending divs on mouseover the appended dives would not disappear when using D3's 'transition' on mouseover/mouseout
129+
// solution: remove all tooltop divs on mouseout
130+
d3.selectAll('.tooltip').remove();
131+
})
105132
.attr('transform', d => `translate(${d.x},${d.y})`);
106133

107-
108134
node.append('circle')
109135
.attr('fill', d => {
110136
if (d.data.index === currLocation.index) {
@@ -124,17 +150,86 @@ function History(props: Record<string, unknown>) {
124150
return svg.node();
125151
};
126152

153+
// findDiff function uses same logic as ActionContainer.tsx
154+
function findDiff(index) {
155+
const statelessCleanning = (obj: {
156+
name?: string;
157+
componentData?: object;
158+
state?: string | any;
159+
stateSnaphot?: object;
160+
children?: any[];
161+
}) => {
162+
const newObj = { ...obj };
163+
if (newObj.name === 'nameless') {
164+
delete newObj.name;
165+
}
166+
if (newObj.componentData) {
167+
delete newObj.componentData;
168+
}
169+
if (newObj.state === 'stateless') {
170+
delete newObj.state;
171+
}
172+
if (newObj.stateSnaphot) {
173+
newObj.stateSnaphot = statelessCleanning(obj.stateSnaphot);
174+
}
175+
if (newObj.children) {
176+
newObj.children = [];
177+
if (obj.children.length > 0) {
178+
obj.children.forEach(
179+
(element: { state?: object | string; children?: [] }) => {
180+
if (
181+
element.state !== 'stateless'
182+
|| element.children.length > 0
183+
) {
184+
const clean = statelessCleanning(element);
185+
newObj.children.push(clean);
186+
}
187+
},
188+
);
189+
}
190+
}
191+
return newObj;
192+
};
193+
194+
const previousDisplay = statelessCleanning(snapshots[index - 1]);
195+
const delta = diff(previousDisplay, snapshots[index]);
196+
const changedState = findStateChangeObj(delta);
197+
// figured out the formatting for hover, applying diff.csss
198+
const html = formatters.html.format(changedState[0]);
199+
// uneeded, not returning a react component in SVG div
200+
// const output = ReactHtmlParser(html);
201+
return html;
202+
}
203+
204+
function findStateChangeObj(delta, changedState = []) {
205+
if (!delta.children && !delta.state) {
206+
return changedState;
207+
}
208+
if (delta.state && delta.state[0] !== 'stateless') {
209+
changedState.push(delta.state);
210+
}
211+
if (!delta.children) {
212+
return changedState;
213+
}
214+
Object.keys(delta.children).forEach(child => {
215+
// if (isNaN(child) === false) {
216+
changedState.push(...findStateChangeObj(delta.children[child]));
217+
// }
218+
});
219+
return changedState;
220+
}
221+
127222
// below we are rendering the LegendKey component and passing hierarchy as props
128223
// then rendering each node in History tab to render using D3, which will share area with LegendKey
129224
return (
130-
<>
225+
<div className="display">
131226
{/* <LegendKey hierarchy={hierarchy} /> */}
132227
<svg
133228
ref={svgRef}
134229
width={totalWidth}
135230
height={totalHeight}
136231
/>
137-
</>
232+
</div>
138233
);
139234
}
140235

src/app/components/StateRoute.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ const StateRoute = (props: StateRouteProps) => {
9090
sliderIndex={sliderIndex}
9191
viewIndex={viewIndex}
9292
currLocation={currLocation}
93+
// added snapshots 11/4 Rob
94+
snapshots={snapshots}
9395
/>
9496
)}
9597
</ParentSize>

src/app/components/WebMetrics.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const radialGraph = props => {
105105
</div>
106106
</Trigger>
107107
<Hover type="hover">
108-
<div style={{ padding: '0.5rem 1rem' }} id="hover-box">
108+
<div style={{ zIndex: 1, position: 'relative', padding: '0.5rem 1rem' }} id="hover-box">
109109
<p>
110110
<strong>{props.name}</strong>
111111
</p>

src/app/containers/ActionContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function ActionContainer(props) {
2424
let actionsArr = [];
2525
const hierarchyArr: any[] = [];
2626

27+
2728
function findDiff(index) {
2829
const statelessCleanning = (obj: {
2930
name?: string;
@@ -154,7 +155,6 @@ function ActionContainer(props) {
154155
}
155156
// Sort by index.
156157
hierarchyArr.sort((a, b) => a.index - b.index);
157-
158158
actionsArr = hierarchyArr.map(
159159
(
160160
snapshot: {

src/app/containers/MainContainer.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const mixpanel = require('mixpanel').init('12fa2800ccbf44a5c36c37bc9776e4c0', {
1818
debug: false,
1919
protocol: 'https',
2020
});
21-
2221
function MainContainer(): any {
2322
const [store, dispatch] = useStoreContext();
2423
const { tabs, currentTab, port: currentPort } = store;

src/app/containers/StateContainer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const StateContainer = (props: StateContainerProps): JSX.Element => {
4141
viewIndex,
4242
webMetrics,
4343
currLocation,
44+
// added snapshots, Rob 11/4
45+
snapshots,
4446
} = props;
4547

4648
return (

src/app/reducers/mainReducer.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { produce, original } from 'immer';
2+
import { debuglog } from 'util';
23

34
import * as types from '../constants/actionTypes.ts';
45

@@ -11,25 +12,27 @@ export default (state, action) => produce(state, draft => {
1112
// eslint-disable-next-line max-len
1213
// function that finds the index in the hierarchy and extracts the name of the equivalent index to add to the post message
1314
// eslint-disable-next-line consistent-return
15+
16+
// (action.payload, hierarchy)
1417
const findName = (index, obj) => {
1518
// eslint-disable-next-line eqeqeq
1619
if (obj && obj.index == index) {
1720
return obj.name;
1821
}
19-
2022
const objChildArray = [];
2123
if (obj) {
24+
// eslint-disable-next-line no-restricted-syntax
2225
for (const objChild of obj.children) {
2326
objChildArray.push(findName(index, objChild));
2427
}
2528
}
29+
// eslint-disable-next-line no-restricted-syntax
2630
for (const objChildName of objChildArray) {
2731
if (objChildName) {
2832
return objChildName;
2933
}
3034
}
3135
};
32-
3336
switch (action.type) {
3437
// Save case will store the series user wants to save to the chrome local storage
3538
case types.SAVE: {
@@ -76,11 +79,12 @@ export default (state, action) => produce(state, draft => {
7679
}
7780

7881
case types.MOVE_BACKWARD: {
79-
if (snapshots.length > 0 && sliderIndex > 0) {
82+
if (sliderIndex > 0) {
8083
const newIndex = sliderIndex - 1;
8184
// eslint-disable-next-line max-len
8285
// finds the name by the newIndex parsing through the hierarchy to send to background.js the current name in the jump action
8386
const nameFromIndex = findName(newIndex, hierarchy);
87+
8488
port.postMessage({
8589
action: 'jumpToSnap',
8690
payload: snapshots[newIndex],
@@ -105,9 +109,9 @@ export default (state, action) => produce(state, draft => {
105109

106110
port.postMessage({
107111
action: 'jumpToSnap',
112+
payload: snapshots[newIndex],
108113
index: newIndex,
109114
name: nameFromIndex,
110-
payload: snapshots[newIndex],
111115
tabId: currentTab,
112116
});
113117

@@ -146,7 +150,8 @@ export default (state, action) => produce(state, draft => {
146150
// eslint-disable-next-line max-len
147151
// finds the name by the action.payload parsing through the hierarchy to send to background.js the current name in the jump action
148152
const nameFromIndex = findName(action.payload, hierarchy);
149-
153+
// nameFromIndex is a number based on which jump button is pushed
154+
150155
port.postMessage({
151156
action: 'jumpToSnap',
152157
payload: snapshots[action.payload],

0 commit comments

Comments
 (0)