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:
parent
28fc8b795b
commit
db1a7ca2c3
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue