refactor: remove immutable from status and auth (#4727)

This commit is contained in:
Vladislav Shkodin 2020-12-20 12:59:25 +02:00 committed by GitHub
parent bf84b6767d
commit 655fffe7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 75 deletions

View File

@ -1,8 +1,8 @@
import { State } from '../types/redux';
import { currentBackend } from '../backend';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux'; import { AnyAction } from 'redux';
import { actions as notifActions } from 'redux-notifications'; import { actions as notifActions } from 'redux-notifications';
import { State } from '../types/redux';
import { currentBackend } from '../backend';
const { notifSend, notifDismiss } = notifActions; const { notifSend, notifDismiss } = notifActions;
@ -37,7 +37,7 @@ export function checkBackendStatus() {
return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => { return async (dispatch: ThunkDispatch<State, {}, AnyAction>, getState: () => State) => {
try { try {
const state = getState(); const state = getState();
if (state.status.get('isFetching')) { if (state.status.isFetching) {
return; return;
} }

View File

@ -73,13 +73,13 @@ const RouteInCollection = ({ collections, render, ...props }) => {
class App extends React.Component { class App extends React.Component {
static propTypes = { static propTypes = {
auth: ImmutablePropTypes.map, auth: PropTypes.object.isRequired,
config: ImmutablePropTypes.map, config: ImmutablePropTypes.map,
collections: ImmutablePropTypes.orderedMap, collections: ImmutablePropTypes.orderedMap,
loadConfig: PropTypes.func.isRequired, loadConfig: PropTypes.func.isRequired,
loginUser: PropTypes.func.isRequired, loginUser: PropTypes.func.isRequired,
logoutUser: PropTypes.func.isRequired, logoutUser: PropTypes.func.isRequired,
user: ImmutablePropTypes.map, user: PropTypes.object,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]), publishMode: PropTypes.oneOf([SIMPLE, EDITORIAL_WORKFLOW]),
siteId: PropTypes.string, siteId: PropTypes.string,
@ -129,9 +129,8 @@ class App extends React.Component {
<Notifs CustomComponent={Toast} /> <Notifs CustomComponent={Toast} />
{React.createElement(backend.authComponent(), { {React.createElement(backend.authComponent(), {
onLogin: this.handleLogin.bind(this), onLogin: this.handleLogin.bind(this),
error: auth && auth.get('error'), error: auth.error,
isFetching: auth && auth.get('isFetching'), inProgress: auth.isFetching,
inProgress: (auth && auth.get('isFetching')) || false,
siteId: this.props.config.getIn(['backend', 'site_domain']), siteId: this.props.config.getIn(['backend', 'site_domain']),
base_url: this.props.config.getIn(['backend', 'base_url'], null), base_url: this.props.config.getIn(['backend', 'base_url'], null),
authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']), authEndpoint: this.props.config.getIn(['backend', 'auth_endpoint']),
@ -262,7 +261,7 @@ class App extends React.Component {
function mapStateToProps(state) { function mapStateToProps(state) {
const { auth, config, collections, globalUI, mediaLibrary } = state; const { auth, config, collections, globalUI, mediaLibrary } = state;
const user = auth && auth.get('user'); const user = auth.user;
const isFetching = globalUI.get('isFetching'); const isFetching = globalUI.get('isFetching');
const publishMode = config && config.get('publish_mode'); const publishMode = config && config.get('publish_mode');
const useMediaLibrary = !mediaLibrary.get('externalLibrary'); const useMediaLibrary = !mediaLibrary.get('externalLibrary');

View File

@ -113,7 +113,7 @@ const AppHeaderNavList = styled.ul`
class Header extends React.Component { class Header extends React.Component {
static propTypes = { static propTypes = {
user: ImmutablePropTypes.map.isRequired, user: PropTypes.object.isRequired,
collections: ImmutablePropTypes.orderedMap.isRequired, collections: ImmutablePropTypes.orderedMap.isRequired,
onCreateEntryClick: PropTypes.func.isRequired, onCreateEntryClick: PropTypes.func.isRequired,
onLogoutClick: PropTypes.func.isRequired, onLogoutClick: PropTypes.func.isRequired,
@ -216,7 +216,7 @@ class Header extends React.Component {
<SettingsDropdown <SettingsDropdown
displayUrl={displayUrl} displayUrl={displayUrl}
isTestRepo={isTestRepo} isTestRepo={isTestRepo}
imageUrl={user.get('avatar_url')} imageUrl={user?.avatar_url}
onLogoutClick={onLogoutClick} onLogoutClick={onLogoutClick}
/> />
</AppHeaderActions> </AppHeaderActions>

View File

@ -67,7 +67,7 @@ export class Editor extends React.Component {
deployPreview: ImmutablePropTypes.map, deployPreview: ImmutablePropTypes.map,
loadDeployPreview: PropTypes.func.isRequired, loadDeployPreview: PropTypes.func.isRequired,
currentStatus: PropTypes.string, currentStatus: PropTypes.string,
user: ImmutablePropTypes.map.isRequired, user: PropTypes.object,
location: PropTypes.shape({ location: PropTypes.shape({
pathname: PropTypes.string, pathname: PropTypes.string,
search: PropTypes.string, search: PropTypes.string,
@ -432,7 +432,7 @@ function mapStateToProps(state, ownProps) {
const newEntry = ownProps.newRecord === true; const newEntry = ownProps.newRecord === true;
const fields = selectFields(collection, slug); const fields = selectFields(collection, slug);
const entry = newEntry ? null : selectEntry(state, collectionName, slug); const entry = newEntry ? null : selectEntry(state, collectionName, slug);
const user = auth && auth.get('user'); const user = auth.user;
const hasChanged = entryDraft.get('hasChanged'); const hasChanged = entryDraft.get('hasChanged');
const displayUrl = config.get('display_url'); const displayUrl = config.get('display_url');
const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW; const hasWorkflow = config.get('publish_mode') === EDITORIAL_WORKFLOW;

View File

@ -410,7 +410,7 @@ EditorInterface.propTypes = {
unPublish: PropTypes.func.isRequired, unPublish: PropTypes.func.isRequired,
onDuplicate: PropTypes.func.isRequired, onDuplicate: PropTypes.func.isRequired,
onChangeStatus: PropTypes.func.isRequired, onChangeStatus: PropTypes.func.isRequired,
user: ImmutablePropTypes.map.isRequired, user: PropTypes.object,
hasChanged: PropTypes.bool, hasChanged: PropTypes.bool,
displayUrl: PropTypes.string, displayUrl: PropTypes.string,
hasWorkflow: PropTypes.bool, hasWorkflow: PropTypes.bool,

View File

@ -233,7 +233,7 @@ class EditorToolbar extends React.Component {
onDuplicate: PropTypes.func.isRequired, onDuplicate: PropTypes.func.isRequired,
onPublishAndNew: PropTypes.func.isRequired, onPublishAndNew: PropTypes.func.isRequired,
onPublishAndDuplicate: PropTypes.func.isRequired, onPublishAndDuplicate: PropTypes.func.isRequired,
user: ImmutablePropTypes.map.isRequired, user: PropTypes.object,
hasChanged: PropTypes.bool, hasChanged: PropTypes.bool,
displayUrl: PropTypes.string, displayUrl: PropTypes.string,
collection: ImmutablePropTypes.map.isRequired, collection: ImmutablePropTypes.map.isRequired,
@ -606,7 +606,7 @@ class EditorToolbar extends React.Component {
<ToolbarSectionMeta> <ToolbarSectionMeta>
<SettingsDropdown <SettingsDropdown
displayUrl={displayUrl} displayUrl={displayUrl}
imageUrl={user.get('avatar_url')} imageUrl={user?.avatar_url}
onLogoutClick={onLogoutClick} onLogoutClick={onLogoutClick}
/> />
</ToolbarSectionMeta> </ToolbarSectionMeta>

View File

@ -1,32 +1,38 @@
import { fromJS } from 'immutable';
import { authenticating, authenticate, authError, logout } from '../../actions/auth'; import { authenticating, authenticate, authError, logout } from '../../actions/auth';
import auth, { defaultState } from '../auth'; import auth, { defaultState } from '../auth';
describe('auth', () => { describe('auth', () => {
it('should handle an empty state', () => { it('should handle an empty state', () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore // @ts-ignore auth reducer doesn't accept empty action
expect(auth(undefined, {})).toEqual(defaultState); expect(auth(undefined, {})).toEqual(defaultState);
}); });
it('should handle an authentication request', () => { it('should handle an authentication request', () => {
expect(auth(undefined, authenticating())).toEqual(defaultState.set('isFetching', true)); expect(auth(undefined, authenticating())).toEqual({
...defaultState,
isFetching: true,
});
}); });
it('should handle authentication', () => { it('should handle authentication', () => {
const user = { name: 'joe', token: 'token' }; const user = { name: 'joe', token: 'token' };
expect(auth(undefined, authenticate(user))).toEqual(defaultState.set('user', fromJS(user))); expect(auth(undefined, authenticate(user))).toEqual({
...defaultState,
user,
});
}); });
it('should handle an authentication error', () => { it('should handle an authentication error', () => {
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual( expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual({
defaultState.set('error', 'Error: Bad credentials'), ...defaultState,
); error: 'Error: Bad credentials',
});
}); });
it('should handle logout', () => { it('should handle logout', () => {
const user = { name: 'joe', token: 'token' }; const user = { name: 'joe', token: 'token' };
const newState = auth(defaultState.set('user', fromJS(user)), logout()); const newState = auth({ ...defaultState, user }, logout());
expect(newState.get('user')).toBeUndefined(); expect(newState.user).toBeUndefined();
}); });
}); });

View File

@ -1,4 +1,4 @@
import { fromJS } from 'immutable'; import { produce } from 'immer';
import { User } from 'netlify-cms-lib-util'; import { User } from 'netlify-cms-lib-util';
import { import {
AUTH_REQUEST, AUTH_REQUEST,
@ -8,35 +8,37 @@ import {
LOGOUT, LOGOUT,
AuthAction, AuthAction,
} from '../actions/auth'; } from '../actions/auth';
import { StaticallyTypedRecord } from '../types/immutable';
export type Auth = StaticallyTypedRecord<{ export type Auth = {
isFetching: boolean; isFetching: boolean;
user: StaticallyTypedRecord<User> | undefined; user: User | undefined;
error: string | undefined; error: string | undefined;
}>; };
export const defaultState = fromJS({ export const defaultState: Auth = {
isFetching: false, isFetching: false,
user: undefined, user: undefined,
error: undefined, error: undefined,
}) as Auth;
const auth = (state = defaultState, action: AuthAction) => {
switch (action.type) {
case AUTH_REQUEST:
return state.set('isFetching', true);
case AUTH_SUCCESS:
return state.set('user', fromJS(action.payload));
case AUTH_FAILURE:
return state.set('error', action.payload && action.payload.toString());
case AUTH_REQUEST_DONE:
return state.set('isFetching', false);
case LOGOUT:
return state.set('user', undefined).set('isFetching', false);
default:
return state;
}
}; };
const auth = produce((state: Auth, action: AuthAction) => {
switch (action.type) {
case AUTH_REQUEST:
state.isFetching = true;
break;
case AUTH_SUCCESS:
state.user = action.payload;
break;
case AUTH_FAILURE:
state.error = action.payload && action.payload.toString();
break;
case AUTH_REQUEST_DONE:
state.isFetching = false;
break;
case LOGOUT:
state.user = undefined;
state.isFetching = false;
}
}, defaultState);
export default auth; export default auth;

View File

@ -1,46 +1,37 @@
import { fromJS } from 'immutable'; import { produce } from 'immer';
import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE, StatusAction } from '../actions/status'; import { STATUS_REQUEST, STATUS_SUCCESS, STATUS_FAILURE, StatusAction } from '../actions/status';
import { StaticallyTypedRecord } from '../types/immutable';
export type Status = StaticallyTypedRecord<{ export type Status = {
isFetching: boolean; isFetching: boolean;
status: StaticallyTypedRecord<{ status: {
auth: StaticallyTypedRecord<{ status: boolean }>; auth: { status: boolean };
api: StaticallyTypedRecord<{ status: boolean; statusPage: string }>; api: { status: boolean; statusPage: string };
}>; };
error: Error | undefined; error: Error | undefined;
}>; };
const defaultState = fromJS({ const defaultState: Status = {
isFetching: false, isFetching: false,
status: { status: {
auth: { status: true }, auth: { status: true },
api: { status: true, statusPage: '' }, api: { status: true, statusPage: '' },
}, },
error: undefined, error: undefined,
}) as Status; };
const status = (state = defaultState, action: StatusAction) => { const status = produce((state: Status, action: StatusAction) => {
switch (action.type) { switch (action.type) {
case STATUS_REQUEST: case STATUS_REQUEST:
return state.set('isFetching', true); state.isFetching = true;
break;
case STATUS_SUCCESS: case STATUS_SUCCESS:
return state.withMutations(map => { state.isFetching = false;
map.set('isFetching', false); state.status = action.payload.status;
map.set('status', fromJS(action.payload.status)); break;
});
case STATUS_FAILURE: case STATUS_FAILURE:
return state.withMutations(map => { state.isFetching = false;
map.set('isFetching', false); state.error = action.payload.error;
map.set('error', action.payload.error);
});
default:
return state;
} }
}; }, defaultState);
export const selectStatus = (status: Status) => {
return status.get('status').toJS();
};
export default status; export default status;