Allow network selection via query parameter (#1496)

* initial non-working design

* continue implementation

* don't export shared type

* delay before requesting currentBlock

* setup one-time node change intent

* working implementation without node pulse

* dispatch action instead of call to enable node pulse

* Remove copy changes

* add test case for delay in handleNodeChangeIntent saga

* don't redundantly change networks

* Cleanup code and add tests (#1504)
This commit is contained in:
Daniel Ternyak 2018-04-12 18:17:46 -05:00 committed by GitHub
parent 28fc8b795b
commit db1a7ca2c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 96 additions and 9 deletions

View File

@ -53,6 +53,14 @@ export function changeNodeIntent(payload: string): interfaces.ChangeNodeIntentAc
};
}
export type TChangeNodeIntentOneTime = typeof changeNodeIntentOneTime;
export function changeNodeIntentOneTime(payload: string): interfaces.ChangeNodeIntentOneTimeAction {
return {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME,
payload
};
}
export type TChangeNodeForce = typeof changeNodeForce;
export function changeNodeForce(payload: string): interfaces.ChangeNodeForceAction {
return {

View File

@ -39,6 +39,13 @@ export interface ChangeNodeIntentAction {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT;
payload: string;
}
/*** Change Node Onetime ***/
export interface ChangeNodeIntentOneTimeAction {
type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME;
payload: string;
}
/*** Force Change Node ***/
export interface ChangeNodeForceAction {
type: TypeKeys.CONFIG_NODE_CHANGE_FORCE;

View File

@ -13,6 +13,7 @@ export enum TypeKeys {
CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET',
CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE',
CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT',
CONFIG_NODE_CHANGE_INTENT_ONETIME = 'CONFIG_NODE_CHANGE_INTENT_ONETIME',
CONFIG_NODE_CHANGE_FORCE = 'CONFIG_NODE_CHANGE_FORCE',
CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE',

View File

@ -1,12 +1,14 @@
import {
TChangeLanguage,
TChangeNodeIntent,
TChangeNodeIntentOneTime,
TAddCustomNode,
TRemoveCustomNode,
TAddCustomNetwork,
AddCustomNodeAction,
changeLanguage,
changeNodeIntent,
changeNodeIntentOneTime,
addCustomNode,
removeCustomNode,
addCustomNetwork
@ -34,15 +36,21 @@ import {
CustomNodeOption,
NodeOption,
getNodeOptions,
getNetworkConfig
getNetworkConfig,
isStaticNodeId
} from 'selectors/config';
import { NetworkConfig } from 'types/network';
import { connect } from 'react-redux';
import { connect, MapStateToProps } from 'react-redux';
import { stripWeb3Network } from 'libs/nodes';
interface OwnProps {
networkParam: string | null;
}
interface DispatchProps {
changeLanguage: TChangeLanguage;
changeNodeIntent: TChangeNodeIntent;
changeNodeIntentOneTime: TChangeNodeIntentOneTime;
setGasPriceField: TSetGasPriceField;
addCustomNode: TAddCustomNode;
removeCustomNode: TRemoveCustomNode;
@ -50,6 +58,7 @@ interface DispatchProps {
}
interface StateProps {
shouldSetNodeFromQS: boolean;
network: NetworkConfig;
languageSelection: AppState['config']['meta']['languageSelection'];
node: NodeConfig;
@ -59,7 +68,11 @@ interface StateProps {
nodeOptions: (CustomNodeOption | NodeOption)[];
}
const mapStateToProps = (state: AppState): StateProps => ({
const mapStateToProps: MapStateToProps<StateProps, OwnProps, AppState> = (
state,
{ networkParam }
): StateProps => ({
shouldSetNodeFromQS: !!(networkParam && isStaticNodeId(state, networkParam)),
isOffline: getOffline(state),
isChangingNode: isNodeChanging(state),
languageSelection: getLanguageSelection(state),
@ -73,6 +86,7 @@ const mapDispatchToProps: DispatchProps = {
setGasPriceField,
changeLanguage,
changeNodeIntent,
changeNodeIntentOneTime,
addCustomNode,
removeCustomNode,
addCustomNetwork
@ -82,13 +96,17 @@ interface State {
isAddingCustomNode: boolean;
}
type Props = StateProps & DispatchProps;
type Props = OwnProps & StateProps & DispatchProps;
class Header extends Component<Props, State> {
public state = {
isAddingCustomNode: false
};
public componentDidMount() {
this.attemptSetNodeFromQueryParameter();
}
public render() {
const {
languageSelection,
@ -220,6 +238,13 @@ class Header extends Component<Props, State> {
this.setState({ isAddingCustomNode: false });
this.props.addCustomNode(payload);
};
private attemptSetNodeFromQueryParameter() {
const { shouldSetNodeFromQS, networkParam } = this.props;
if (shouldSetNodeFromQS) {
this.props.changeNodeIntentOneTime(networkParam!);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View File

@ -13,7 +13,15 @@ interface IQueryResults {
[key: string]: string | null;
}
export type Param = 'to' | 'data' | 'readOnly' | 'tokenSymbol' | 'value' | 'gaslimit' | 'limit';
export type Param =
| 'to'
| 'data'
| 'readOnly'
| 'tokenSymbol'
| 'value'
| 'gaslimit'
| 'limit'
| 'network';
interface Props extends RouteComponentProps<{}> {
params: Param[];

View File

@ -5,6 +5,7 @@ import { AppState } from 'reducers';
import Notifications from './Notifications';
import OfflineTab from './OfflineTab';
import { getOffline, getLatestBlock } from 'selectors/config';
import { Query } from 'components/renderCbs';
interface StateProps {
isOffline: AppState['config']['meta']['offline'];
@ -24,7 +25,12 @@ class TabSection extends Component<Props, {}> {
return (
<div className="page-layout">
<Header />
<Query
params={['network']}
withQuery={({ network }) => (
<Header networkParam={network && `${network.toLowerCase()}_auto`} />
)}
/>
<div className="Tab container">
{isUnavailableOffline && isOffline ? <OfflineTab /> : children}
</div>

View File

@ -28,7 +28,8 @@ import {
setLatestBlock,
AddCustomNodeAction,
ChangeNodeForceAction,
ChangeNodeIntentAction
ChangeNodeIntentAction,
ChangeNodeIntentOneTimeAction
} from 'actions/config';
import { showNotification } from 'actions/notifications';
import { resetWallet } from 'actions/wallet';
@ -103,6 +104,15 @@ export function* reload(): SagaIterator {
setTimeout(() => location.reload(), 1150);
}
export function* handleNodeChangeIntentOneTime(): SagaIterator {
const action: ChangeNodeIntentOneTimeAction = yield take(
TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME
);
// allow shepherdProvider async init to complete. TODO - don't export shepherdProvider as promise
yield call(delay, 100);
yield put(changeNodeIntent(action.payload));
}
export function* handleNodeChangeIntent({
payload: nodeIdToSwitchTo
}: ChangeNodeIntentAction): SagaIterator {
@ -210,6 +220,7 @@ export function* handleNodeChangeForce({ payload: staticNodeIdToSwitchTo }: Chan
}
export const node = [
fork(handleNodeChangeIntentOneTime),
takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent),
takeEvery(TypeKeys.CONFIG_NODE_CHANGE_FORCE, handleNodeChangeForce),
takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus),

View File

@ -8,13 +8,17 @@ import {
changeNode,
changeNodeIntent,
changeNodeForce,
setLatestBlock
setLatestBlock,
TypeKeys,
ChangeNodeIntentOneTimeAction,
changeNodeIntentOneTime
} from 'actions/config';
import {
handleNodeChangeIntent,
handlePollOfflineStatus,
pollOfflineStatus,
handleNewNetwork
handleNewNetwork,
handleNodeChangeIntentOneTime
} from 'sagas/config/node';
import {
getNodeId,
@ -243,6 +247,23 @@ describe('handleNodeChangeIntent*', () => {
});
});
describe('handleNodeChangeIntentOneTime', () => {
const saga = handleNodeChangeIntentOneTime();
const action: ChangeNodeIntentOneTimeAction = changeNodeIntentOneTime('eth_auto');
it('should take a one time action based on the url containing a valid network to switch to', () => {
expect(saga.next().value).toEqual(take(TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME));
});
it(`should delay for 10 ms to allow shepherdProvider async init to complete`, () => {
expect(saga.next(action).value).toEqual(call(delay, 100));
});
it('should dispatch the change node intent', () => {
expect(saga.next().value).toEqual(put(changeNodeIntent(action.payload)));
});
it('should be done', () => {
expect(saga.next().done).toEqual(true);
});
});
describe('unsetWeb3Node*', () => {
const previousNodeId = 'eth_mycrypto';
const mockNodeId = 'web3';