mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-14 19:43:49 +00:00
Merge pull request #724 from acelaya-forks/feature/more-rtk
Feature/more rtk
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '../../utils/helpers/redux';
|
||||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||||
import { ShlinkDomainRedirects } from '../../api/types';
|
import { ShlinkDomainRedirects } from '../../api/types';
|
||||||
import { ShlinkState } from '../../container/types';
|
|
||||||
|
|
||||||
const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS';
|
const EDIT_DOMAIN_REDIRECTS = 'shlink/domainRedirects/EDIT_DOMAIN_REDIRECTS';
|
||||||
|
|
||||||
@@ -12,11 +11,11 @@ export interface EditDomainRedirects {
|
|||||||
|
|
||||||
export const editDomainRedirects = (
|
export const editDomainRedirects = (
|
||||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||||
) => createAsyncThunk<EditDomainRedirects, EditDomainRedirects, { state: ShlinkState }>(
|
) => createAsyncThunk(
|
||||||
EDIT_DOMAIN_REDIRECTS,
|
EDIT_DOMAIN_REDIRECTS,
|
||||||
async ({ domain, redirects: domainRedirects }, { getState }) => {
|
async ({ domain, redirects: providedRedirects }: EditDomainRedirects, { getState }): Promise<EditDomainRedirects> => {
|
||||||
const { editDomainRedirects: shlinkEditDomainRedirects } = buildShlinkApiClient(getState);
|
const { editDomainRedirects: shlinkEditDomainRedirects } = buildShlinkApiClient(getState);
|
||||||
const redirects = await shlinkEditDomainRedirects({ domain, ...domainRedirects });
|
const redirects = await shlinkEditDomainRedirects({ domain, ...providedRedirects });
|
||||||
|
|
||||||
return { domain, redirects };
|
return { domain, redirects };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSlice, createAsyncThunk, createAction, SliceCaseReducers, AsyncThunk } from '@reduxjs/toolkit';
|
import { createSlice, createAction, SliceCaseReducers, AsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import { createAsyncThunk } from '../../utils/helpers/redux';
|
||||||
import { ShlinkDomainRedirects } from '../../api/types';
|
import { ShlinkDomainRedirects } from '../../api/types';
|
||||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||||
import { ShlinkState } from '../../container/types';
|
|
||||||
import { Domain, DomainStatus } from '../data';
|
import { Domain, DomainStatus } from '../data';
|
||||||
import { hasServerData } from '../../servers/data';
|
import { hasServerData } from '../../servers/data';
|
||||||
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
|
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
|
||||||
@@ -49,22 +49,19 @@ export const domainsListReducerCreator = (
|
|||||||
buildShlinkApiClient: ShlinkApiClientBuilder,
|
buildShlinkApiClient: ShlinkApiClientBuilder,
|
||||||
editDomainRedirects: AsyncThunk<EditDomainRedirects, any, any>,
|
editDomainRedirects: AsyncThunk<EditDomainRedirects, any, any>,
|
||||||
) => {
|
) => {
|
||||||
const listDomains = createAsyncThunk<ListDomains, void, { state: ShlinkState }>(
|
const listDomains = createAsyncThunk(LIST_DOMAINS, async (_: void, { getState }): Promise<ListDomains> => {
|
||||||
LIST_DOMAINS,
|
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
||||||
async (_, { getState }) => {
|
const { data, defaultRedirects } = await shlinkListDomains();
|
||||||
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
|
||||||
const { data, defaultRedirects } = await shlinkListDomains();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
||||||
defaultRedirects,
|
defaultRedirects,
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const checkDomainHealth = createAsyncThunk<ValidateDomain, string, { state: ShlinkState }>(
|
const checkDomainHealth = createAsyncThunk(
|
||||||
VALIDATE_DOMAIN,
|
VALIDATE_DOMAIN,
|
||||||
async (domain: string, { getState }) => {
|
async (domain: string, { getState }): Promise<ValidateDomain> => {
|
||||||
const { selectedServer } = getState();
|
const { selectedServer } = getState();
|
||||||
|
|
||||||
if (!hasServerData(selectedServer)) {
|
if (!hasServerData(selectedServer)) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import { createAsyncThunk } from '../../utils/helpers/redux';
|
||||||
import { ShlinkMercureInfo } from '../../api/types';
|
import { ShlinkMercureInfo } from '../../api/types';
|
||||||
import { ShlinkState } from '../../container/types';
|
|
||||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||||
|
|
||||||
const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
|
const GET_MERCURE_INFO = 'shlink/mercure/GET_MERCURE_INFO';
|
||||||
@@ -17,17 +17,14 @@ const initialState: MercureInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const mercureInfoReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
export const mercureInfoReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||||
const loadMercureInfo = createAsyncThunk<ShlinkMercureInfo, void, { state: ShlinkState }>(
|
const loadMercureInfo = createAsyncThunk(GET_MERCURE_INFO, (_: void, { getState }): Promise<ShlinkMercureInfo> => {
|
||||||
GET_MERCURE_INFO,
|
const { settings } = getState();
|
||||||
async (_, { getState }) => {
|
if (!settings.realTimeUpdates.enabled) {
|
||||||
const { settings } = getState();
|
throw new Error('Real time updates not enabled');
|
||||||
if (!settings.realTimeUpdates.enabled) {
|
}
|
||||||
throw new Error('Real time updates not enabled');
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildShlinkApiClient(getState).mercureInfo();
|
return buildShlinkApiClient(getState).mercureInfo();
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const { reducer } = createSlice({
|
const { reducer } = createSlice({
|
||||||
name: 'mercureInfoReducer',
|
name: 'mercureInfoReducer',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IContainer } from 'bottlejs';
|
import { IContainer } from 'bottlejs';
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import serversReducer from '../servers/reducers/servers';
|
import { serversReducer } from '../servers/reducers/servers';
|
||||||
import selectedServerReducer from '../servers/reducers/selectedServer';
|
import selectedServerReducer from '../servers/reducers/selectedServer';
|
||||||
import shortUrlsListReducer from '../short-urls/reducers/shortUrlsList';
|
import shortUrlsListReducer from '../short-urls/reducers/shortUrlsList';
|
||||||
import shortUrlCreationReducer from '../short-urls/reducers/shortUrlCreation';
|
import shortUrlCreationReducer from '../short-urls/reducers/shortUrlCreation';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { DuplicatedServersModal } from './helpers/DuplicatedServersModal';
|
|||||||
const SHOW_IMPORT_MSG_TIME = 4000;
|
const SHOW_IMPORT_MSG_TIME = 4000;
|
||||||
|
|
||||||
interface CreateServerProps {
|
interface CreateServerProps {
|
||||||
createServer: (server: ServerWithId) => void;
|
createServers: (servers: ServerWithId[]) => void;
|
||||||
servers: ServersMap;
|
servers: ServersMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ const ImportResult = ({ type }: { type: 'error' | 'success' }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useTimeoutToggle: TimeoutToggle) => (
|
export const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useTimeoutToggle: TimeoutToggle) => (
|
||||||
{ servers, createServer }: CreateServerProps,
|
{ servers, createServers }: CreateServerProps,
|
||||||
) => {
|
) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const goBack = useGoBack();
|
const goBack = useGoBack();
|
||||||
@@ -43,7 +43,7 @@ export const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useTim
|
|||||||
|
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
|
||||||
createServer({ ...serverData, id });
|
createServers([{ ...serverData, id }]);
|
||||||
navigate(`/server/${id}`);
|
navigate(`/server/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { identity, memoizeWith, pipe } from 'ramda';
|
import { identity, memoizeWith, pipe } from 'ramda';
|
||||||
import { Action, Dispatch } from 'redux';
|
import { Action, Dispatch } from 'redux';
|
||||||
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
|
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
|
||||||
@@ -14,9 +15,7 @@ export const MIN_FALLBACK_VERSION = '1.0.0';
|
|||||||
export const MAX_FALLBACK_VERSION = '999.999.999';
|
export const MAX_FALLBACK_VERSION = '999.999.999';
|
||||||
export const LATEST_VERSION_CONSTRAINT = 'latest';
|
export const LATEST_VERSION_CONSTRAINT = 'latest';
|
||||||
|
|
||||||
export interface SelectServerAction extends Action<string> {
|
export type SelectServerAction = PayloadAction<SelectedServer>;
|
||||||
selectedServer: SelectedServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionToSemVer = pipe(
|
const versionToSemVer = pipe(
|
||||||
(version: string) => (version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version),
|
(version: string) => (version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version),
|
||||||
@@ -35,7 +34,7 @@ const initialState: SelectedServer = null;
|
|||||||
|
|
||||||
export default buildReducer<SelectedServer, SelectServerAction>({
|
export default buildReducer<SelectedServer, SelectServerAction>({
|
||||||
[RESET_SELECTED_SERVER]: () => initialState,
|
[RESET_SELECTED_SERVER]: () => initialState,
|
||||||
[SELECT_SERVER]: (_, { selectedServer }) => selectedServer,
|
[SELECT_SERVER]: (_, { payload }) => payload,
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
export const resetSelectedServer = buildActionCreator(RESET_SELECTED_SERVER);
|
export const resetSelectedServer = buildActionCreator(RESET_SELECTED_SERVER);
|
||||||
@@ -57,7 +56,7 @@ export const selectServer = (
|
|||||||
if (!selectedServer) {
|
if (!selectedServer) {
|
||||||
dispatch<SelectServerAction>({
|
dispatch<SelectServerAction>({
|
||||||
type: SELECT_SERVER,
|
type: SELECT_SERVER,
|
||||||
selectedServer: { serverNotFound: true },
|
payload: { serverNotFound: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -69,7 +68,7 @@ export const selectServer = (
|
|||||||
|
|
||||||
dispatch<SelectServerAction>({
|
dispatch<SelectServerAction>({
|
||||||
type: SELECT_SERVER,
|
type: SELECT_SERVER,
|
||||||
selectedServer: {
|
payload: {
|
||||||
...selectedServer,
|
...selectedServer,
|
||||||
version,
|
version,
|
||||||
printableVersion,
|
printableVersion,
|
||||||
@@ -79,7 +78,7 @@ export const selectServer = (
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch<SelectServerAction>({
|
dispatch<SelectServerAction>({
|
||||||
type: SELECT_SERVER,
|
type: SELECT_SERVER,
|
||||||
selectedServer: { ...selectedServer, serverNotReachable: true },
|
payload: { ...selectedServer, serverNotReachable: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
|
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { Action } from 'redux';
|
|
||||||
import { ServerData, ServersMap, ServerWithId } from '../data';
|
import { ServerData, ServersMap, ServerWithId } from '../data';
|
||||||
import { buildReducer } from '../../utils/helpers/redux';
|
|
||||||
|
|
||||||
export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER';
|
interface EditServer {
|
||||||
export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER';
|
|
||||||
export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS';
|
|
||||||
export const SET_AUTO_CONNECT = 'shlink/servers/SET_AUTO_CONNECT';
|
|
||||||
|
|
||||||
export interface CreateServersAction extends Action<string> {
|
|
||||||
newServers: ServersMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DeleteServerAction extends Action<string> {
|
|
||||||
serverId: string;
|
serverId: string;
|
||||||
|
serverData: Partial<ServerData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SetAutoConnectAction extends Action<string> {
|
interface SetAutoConnect {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
autoConnect: boolean;
|
autoConnect: boolean;
|
||||||
}
|
}
|
||||||
@@ -32,50 +23,57 @@ const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
|
|||||||
return assoc('id', uuid(), server);
|
return assoc('id', uuid(), server);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default buildReducer<ServersMap, CreateServersAction & DeleteServerAction & SetAutoConnectAction>({
|
|
||||||
[CREATE_SERVERS]: (state, { newServers }) => ({ ...state, ...newServers }),
|
|
||||||
[DELETE_SERVER]: (state, { serverId }) => dissoc(serverId, state),
|
|
||||||
[EDIT_SERVER]: (state, { serverId, serverData }: any) => (
|
|
||||||
!state[serverId] ? state : assoc(serverId, { ...state[serverId], ...serverData }, state)
|
|
||||||
),
|
|
||||||
[SET_AUTO_CONNECT]: (state, { serverId, autoConnect }) => {
|
|
||||||
if (!state[serverId]) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!autoConnect) {
|
|
||||||
return assoc(serverId, { ...state[serverId], autoConnect }, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fromPairs(
|
|
||||||
toPairs(state).map(([evaluatedServerId, server]) => [
|
|
||||||
evaluatedServerId,
|
|
||||||
{ ...server, autoConnect: evaluatedServerId === serverId },
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}, initialState);
|
|
||||||
|
|
||||||
const serversListToMap = reduce<ServerWithId, ServersMap>((acc, server) => assoc(server.id, server, acc), {});
|
const serversListToMap = reduce<ServerWithId, ServersMap>((acc, server) => assoc(server.id, server, acc), {});
|
||||||
|
|
||||||
export const createServers = pipe(
|
export const { actions, reducer } = createSlice({
|
||||||
map(serverWithId),
|
name: 'serversReducer',
|
||||||
serversListToMap,
|
initialState,
|
||||||
(newServers: ServersMap) => ({ type: CREATE_SERVERS, newServers }),
|
reducers: {
|
||||||
);
|
editServer: {
|
||||||
|
prepare: (serverId: string, serverData: Partial<ServerData>) => ({
|
||||||
|
payload: { serverId, serverData },
|
||||||
|
}),
|
||||||
|
reducer: (state, { payload }: PayloadAction<EditServer>) => {
|
||||||
|
const { serverId, serverData } = payload;
|
||||||
|
return (
|
||||||
|
!state[serverId] ? state : assoc(serverId, { ...state[serverId], ...serverData }, state)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
deleteServer: (state, { payload }) => dissoc(payload.id, state),
|
||||||
|
setAutoConnect: {
|
||||||
|
prepare: ({ id: serverId }: ServerWithId, autoConnect: boolean) => ({
|
||||||
|
payload: { serverId, autoConnect },
|
||||||
|
}),
|
||||||
|
reducer: (state, { payload }: PayloadAction<SetAutoConnect>) => {
|
||||||
|
const { serverId, autoConnect } = payload;
|
||||||
|
if (!state[serverId]) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
export const createServer = (server: ServerWithId) => createServers([server]);
|
if (!autoConnect) {
|
||||||
|
return assoc(serverId, { ...state[serverId], autoConnect }, state);
|
||||||
|
}
|
||||||
|
|
||||||
export const editServer = (serverId: string, serverData: Partial<ServerData>) => ({
|
return fromPairs(
|
||||||
type: EDIT_SERVER,
|
toPairs(state).map(([evaluatedServerId, server]) => [
|
||||||
serverId,
|
evaluatedServerId,
|
||||||
serverData,
|
{ ...server, autoConnect: evaluatedServerId === serverId },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createServers: {
|
||||||
|
prepare: pipe(
|
||||||
|
map(serverWithId),
|
||||||
|
serversListToMap,
|
||||||
|
(payload: ServersMap) => ({ payload }),
|
||||||
|
),
|
||||||
|
reducer: (state, { payload: newServers }: PayloadAction<ServersMap>) => ({ ...state, ...newServers }),
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const deleteServer = ({ id }: ServerWithId): DeleteServerAction => ({ type: DELETE_SERVER, serverId: id });
|
export const { editServer, deleteServer, setAutoConnect, createServers } = actions;
|
||||||
|
|
||||||
export const setAutoConnect = ({ id }: ServerWithId, autoConnect: boolean): SetAutoConnectAction => ({
|
export const serversReducer = reducer;
|
||||||
type: SET_AUTO_CONNECT,
|
|
||||||
serverId: id,
|
|
||||||
autoConnect,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { DeleteServerButton } from '../DeleteServerButton';
|
|||||||
import { EditServer } from '../EditServer';
|
import { EditServer } from '../EditServer';
|
||||||
import { ImportServersBtn } from '../helpers/ImportServersBtn';
|
import { ImportServersBtn } from '../helpers/ImportServersBtn';
|
||||||
import { resetSelectedServer, selectServer } from '../reducers/selectedServer';
|
import { resetSelectedServer, selectServer } from '../reducers/selectedServer';
|
||||||
import { createServer, createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
|
import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
|
||||||
import { fetchServers } from '../reducers/remoteServers';
|
import { fetchServers } from '../reducers/remoteServers';
|
||||||
import { ServerError } from '../helpers/ServerError';
|
import { ServerError } from '../helpers/ServerError';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
@@ -38,7 +38,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
|
|
||||||
bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useTimeoutToggle');
|
bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useTimeoutToggle');
|
||||||
bottle.decorator('CreateServer', withoutSelectedServer);
|
bottle.decorator('CreateServer', withoutSelectedServer);
|
||||||
bottle.decorator('CreateServer', connect(['selectedServer', 'servers'], ['createServer', 'resetSelectedServer']));
|
bottle.decorator('CreateServer', connect(['selectedServer', 'servers'], ['createServers', 'resetSelectedServer']));
|
||||||
|
|
||||||
bottle.serviceFactory('EditServer', EditServer, 'ServerError');
|
bottle.serviceFactory('EditServer', EditServer, 'ServerError');
|
||||||
bottle.decorator('EditServer', connect(['selectedServer'], ['editServer', 'selectServer', 'resetSelectedServer']));
|
bottle.decorator('EditServer', connect(['selectedServer'], ['editServer', 'selectServer', 'resetSelectedServer']));
|
||||||
@@ -70,7 +70,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('selectServer', selectServer, 'buildShlinkApiClient', 'loadMercureInfo');
|
bottle.serviceFactory('selectServer', selectServer, 'buildShlinkApiClient', 'loadMercureInfo');
|
||||||
bottle.serviceFactory('createServer', () => createServer);
|
|
||||||
bottle.serviceFactory('createServers', () => createServers);
|
bottle.serviceFactory('createServers', () => createServers);
|
||||||
bottle.serviceFactory('deleteServer', () => deleteServer);
|
bottle.serviceFactory('deleteServer', () => deleteServer);
|
||||||
bottle.serviceFactory('editServer', () => editServer);
|
bottle.serviceFactory('editServer', () => editServer);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
import { createAsyncThunk as baseCreateAsyncThunk, AsyncThunkPayloadCreator } from '@reduxjs/toolkit';
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
import { ShlinkState } from '../../container/types';
|
||||||
|
|
||||||
type ActionHandler<State, AT> = (currentState: State, action: AT) => State;
|
type ActionHandler<State, AT> = (currentState: State, action: AT) => State;
|
||||||
type ActionHandlerMap<State, AT> = Record<string, ActionHandler<State, AT>>;
|
type ActionHandlerMap<State, AT> = Record<string, ActionHandler<State, AT>>;
|
||||||
@@ -15,3 +17,8 @@ export const buildReducer = <State, AT extends Action>(map: ActionHandlerMap<Sta
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const buildActionCreator = <T extends string>(type: T) => (): Action<T> => ({ type });
|
export const buildActionCreator = <T extends string>(type: T) => (): Action<T> => ({ type });
|
||||||
|
|
||||||
|
export const createAsyncThunk = <Returned, ThunkArg>(
|
||||||
|
typePrefix: string,
|
||||||
|
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, { state: ShlinkState }>,
|
||||||
|
) => baseCreateAsyncThunk(typePrefix, payloadCreator);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { renderWithEvents } from '../__helpers__/setUpTest';
|
|||||||
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: jest.fn() }));
|
||||||
|
|
||||||
describe('<CreateServer />', () => {
|
describe('<CreateServer />', () => {
|
||||||
const createServerMock = jest.fn();
|
const createServersMock = jest.fn();
|
||||||
const navigate = jest.fn();
|
const navigate = jest.fn();
|
||||||
const servers = { foo: Mock.of<ServerWithId>({ url: 'https://existing_url.com', apiKey: 'existing_api_key' }) };
|
const servers = { foo: Mock.of<ServerWithId>({ url: 'https://existing_url.com', apiKey: 'existing_api_key' }) };
|
||||||
const setUp = (serversImported = false, importFailed = false) => {
|
const setUp = (serversImported = false, importFailed = false) => {
|
||||||
@@ -22,7 +22,7 @@ describe('<CreateServer />', () => {
|
|||||||
});
|
});
|
||||||
const CreateServer = createCreateServer(() => <>ImportServersBtn</>, useTimeoutToggle);
|
const CreateServer = createCreateServer(() => <>ImportServersBtn</>, useTimeoutToggle);
|
||||||
|
|
||||||
return renderWithEvents(<CreateServer createServer={createServerMock} servers={servers} />);
|
return renderWithEvents(<CreateServer createServers={createServersMock} servers={servers} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(jest.clearAllMocks);
|
beforeEach(jest.clearAllMocks);
|
||||||
@@ -48,18 +48,18 @@ describe('<CreateServer />', () => {
|
|||||||
it('creates server data when form is submitted', async () => {
|
it('creates server data when form is submitted', async () => {
|
||||||
const { user } = setUp();
|
const { user } = setUp();
|
||||||
|
|
||||||
expect(createServerMock).not.toHaveBeenCalled();
|
expect(createServersMock).not.toHaveBeenCalled();
|
||||||
|
|
||||||
await user.type(screen.getByLabelText(/^Name/), 'the_name');
|
await user.type(screen.getByLabelText(/^Name/), 'the_name');
|
||||||
await user.type(screen.getByLabelText(/^URL/), 'https://the_url.com');
|
await user.type(screen.getByLabelText(/^URL/), 'https://the_url.com');
|
||||||
await user.type(screen.getByLabelText(/^API key/), 'the_api_key');
|
await user.type(screen.getByLabelText(/^API key/), 'the_api_key');
|
||||||
fireEvent.submit(screen.getByRole('form'));
|
fireEvent.submit(screen.getByRole('form'));
|
||||||
|
|
||||||
expect(createServerMock).toHaveBeenCalledWith(expect.objectContaining({
|
expect(createServersMock).toHaveBeenCalledWith([expect.objectContaining({
|
||||||
name: 'the_name',
|
name: 'the_name',
|
||||||
url: 'https://the_url.com',
|
url: 'https://the_url.com',
|
||||||
apiKey: 'the_api_key',
|
apiKey: 'the_api_key',
|
||||||
}));
|
})]);
|
||||||
expect(navigate).toHaveBeenCalledWith(expect.stringMatching(/^\/server\//));
|
expect(navigate).toHaveBeenCalledWith(expect.stringMatching(/^\/server\//));
|
||||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -75,7 +75,7 @@ describe('<CreateServer />', () => {
|
|||||||
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
|
||||||
await user.click(screen.getByRole('button', { name: 'Discard' }));
|
await user.click(screen.getByRole('button', { name: 'Discard' }));
|
||||||
|
|
||||||
expect(createServerMock).not.toHaveBeenCalled();
|
expect(createServersMock).not.toHaveBeenCalled();
|
||||||
expect(navigate).toHaveBeenCalledWith(-1);
|
expect(navigate).toHaveBeenCalledWith(-1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
import { fetchServers } from '../../../src/servers/reducers/remoteServers';
|
import { fetchServers } from '../../../src/servers/reducers/remoteServers';
|
||||||
import { CREATE_SERVERS } from '../../../src/servers/reducers/servers';
|
import { createServers } from '../../../src/servers/reducers/servers';
|
||||||
|
|
||||||
describe('remoteServersReducer', () => {
|
describe('remoteServersReducer', () => {
|
||||||
afterEach(jest.clearAllMocks);
|
afterEach(jest.clearAllMocks);
|
||||||
@@ -87,7 +87,7 @@ describe('remoteServersReducer', () => {
|
|||||||
|
|
||||||
await fetchServers(axios)()(dispatch);
|
await fetchServers(axios)()(dispatch);
|
||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledWith({ type: CREATE_SERVERS, newServers: expectedNewServers });
|
expect(dispatch).toHaveBeenCalledWith({ type: createServers.toString(), payload: expectedNewServers });
|
||||||
expect(get).toHaveBeenCalledTimes(1);
|
expect(get).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import { NonReachableServer, NotFoundServer, RegularServer } from '../../../src/
|
|||||||
describe('selectedServerReducer', () => {
|
describe('selectedServerReducer', () => {
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns default when action is RESET_SELECTED_SERVER', () =>
|
it('returns default when action is RESET_SELECTED_SERVER', () =>
|
||||||
expect(reducer(null, { type: RESET_SELECTED_SERVER, selectedServer: null })).toBeNull());
|
expect(reducer(null, { type: RESET_SELECTED_SERVER, payload: null })).toBeNull());
|
||||||
|
|
||||||
it('returns selected server when action is SELECT_SERVER', () => {
|
it('returns selected server when action is SELECT_SERVER', () => {
|
||||||
const selectedServer = Mock.of<RegularServer>({ id: 'abc123' });
|
const payload = Mock.of<RegularServer>({ id: 'abc123' });
|
||||||
|
|
||||||
expect(reducer(null, { type: SELECT_SERVER, selectedServer })).toEqual(selectedServer);
|
expect(reducer(null, { type: SELECT_SERVER, payload })).toEqual(payload);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ describe('selectedServerReducer', () => {
|
|||||||
|
|
||||||
expect(dispatch).toHaveBeenCalledTimes(3);
|
expect(dispatch).toHaveBeenCalledTimes(3);
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(1, { type: RESET_SELECTED_SERVER });
|
expect(dispatch).toHaveBeenNthCalledWith(1, { type: RESET_SELECTED_SERVER });
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer });
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, payload: expectedSelectedServer });
|
||||||
expect(loadMercureInfo).toHaveBeenCalledTimes(1);
|
expect(loadMercureInfo).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ describe('selectedServerReducer', () => {
|
|||||||
await selectServer(buildApiClient, loadMercureInfo)(id)(dispatch, getState);
|
await selectServer(buildApiClient, loadMercureInfo)(id)(dispatch, getState);
|
||||||
|
|
||||||
expect(apiClientMock.health).toHaveBeenCalled();
|
expect(apiClientMock.health).toHaveBeenCalled();
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer });
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, payload: expectedSelectedServer });
|
||||||
expect(loadMercureInfo).not.toHaveBeenCalled();
|
expect(loadMercureInfo).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ describe('selectedServerReducer', () => {
|
|||||||
|
|
||||||
expect(getState).toHaveBeenCalled();
|
expect(getState).toHaveBeenCalled();
|
||||||
expect(apiClientMock.health).not.toHaveBeenCalled();
|
expect(apiClientMock.health).not.toHaveBeenCalled();
|
||||||
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, selectedServer: expectedSelectedServer });
|
expect(dispatch).toHaveBeenNthCalledWith(2, { type: SELECT_SERVER, payload: expectedSelectedServer });
|
||||||
expect(loadMercureInfo).not.toHaveBeenCalled();
|
expect(loadMercureInfo).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
import { dissoc, values } from 'ramda';
|
import { dissoc, values } from 'ramda';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import reducer, {
|
import {
|
||||||
createServer,
|
|
||||||
deleteServer,
|
deleteServer,
|
||||||
createServers,
|
createServers,
|
||||||
editServer,
|
editServer,
|
||||||
setAutoConnect,
|
setAutoConnect,
|
||||||
EDIT_SERVER,
|
serversReducer,
|
||||||
DELETE_SERVER,
|
|
||||||
CREATE_SERVERS,
|
|
||||||
SET_AUTO_CONNECT,
|
|
||||||
} from '../../../src/servers/reducers/servers';
|
} from '../../../src/servers/reducers/servers';
|
||||||
import { RegularServer } from '../../../src/servers/data';
|
import { RegularServer } from '../../../src/servers/data';
|
||||||
|
|
||||||
describe('serverReducer', () => {
|
describe('serversReducer', () => {
|
||||||
const list = {
|
const list = {
|
||||||
abc123: Mock.of<RegularServer>({ id: 'abc123' }),
|
abc123: Mock.of<RegularServer>({ id: 'abc123' }),
|
||||||
def456: Mock.of<RegularServer>({ id: 'def456' }),
|
def456: Mock.of<RegularServer>({ id: 'def456' }),
|
||||||
@@ -23,35 +19,38 @@ describe('serverReducer', () => {
|
|||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns edited server when action is EDIT_SERVER', () =>
|
it('returns edited server when action is EDIT_SERVER', () =>
|
||||||
expect(reducer(
|
expect(serversReducer(list, {
|
||||||
list,
|
type: editServer.toString(),
|
||||||
{ type: EDIT_SERVER, serverId: 'abc123', serverData: { foo: 'foo' } } as any,
|
payload: { serverId: 'abc123', serverData: { foo: 'foo' } },
|
||||||
)).toEqual({
|
})).toEqual({
|
||||||
abc123: { id: 'abc123', foo: 'foo' },
|
abc123: { id: 'abc123', foo: 'foo' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('returns as it is when action is EDIT_SERVER and server does not exist', () =>
|
it('returns as it is when action is EDIT_SERVER and server does not exist', () =>
|
||||||
expect(reducer(
|
expect(serversReducer(list, {
|
||||||
list,
|
type: editServer.toString(),
|
||||||
{ type: EDIT_SERVER, serverId: 'invalid', serverData: { foo: 'foo' } } as any,
|
payload: { serverId: 'invalid', serverData: { foo: 'foo' } },
|
||||||
)).toEqual({
|
})).toEqual({
|
||||||
abc123: { id: 'abc123' },
|
abc123: { id: 'abc123' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('removes server when action is DELETE_SERVER', () =>
|
it('removes server when action is DELETE_SERVER', () =>
|
||||||
expect(reducer(list, { type: DELETE_SERVER, serverId: 'abc123' } as any)).toEqual({
|
expect(serversReducer(list, {
|
||||||
|
type: deleteServer.toString(),
|
||||||
|
payload: { id: 'abc123' },
|
||||||
|
})).toEqual({
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('appends server when action is CREATE_SERVERS', () =>
|
it('appends server when action is CREATE_SERVERS', () =>
|
||||||
expect(reducer(list, {
|
expect(serversReducer(list, {
|
||||||
type: CREATE_SERVERS,
|
type: createServers.toString(),
|
||||||
newServers: {
|
payload: {
|
||||||
ghi789: { id: 'ghi789' },
|
ghi789: { id: 'ghi789' },
|
||||||
},
|
},
|
||||||
} as any)).toEqual({
|
})).toEqual({
|
||||||
abc123: { id: 'abc123' },
|
abc123: { id: 'abc123' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
ghi789: { id: 'ghi789' },
|
ghi789: { id: 'ghi789' },
|
||||||
@@ -61,11 +60,10 @@ describe('serverReducer', () => {
|
|||||||
[true],
|
[true],
|
||||||
[false],
|
[false],
|
||||||
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
|
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
|
||||||
expect(reducer(list, {
|
expect(serversReducer(list, {
|
||||||
type: SET_AUTO_CONNECT,
|
type: setAutoConnect.toString(),
|
||||||
serverId: 'invalid',
|
payload: { serverId: 'invalid', autoConnect },
|
||||||
autoConnect,
|
})).toEqual({
|
||||||
} as any)).toEqual({
|
|
||||||
abc123: { id: 'abc123' },
|
abc123: { id: 'abc123' },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
}));
|
}));
|
||||||
@@ -76,11 +74,10 @@ describe('serverReducer', () => {
|
|||||||
abc123: { ...list.abc123, autoConnect: true },
|
abc123: { ...list.abc123, autoConnect: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(reducer(listWithDisabledAutoConnect, {
|
expect(serversReducer(listWithDisabledAutoConnect, {
|
||||||
type: SET_AUTO_CONNECT,
|
type: setAutoConnect.toString(),
|
||||||
serverId: 'abc123',
|
payload: { serverId: 'abc123', autoConnect: false },
|
||||||
autoConnect: false,
|
})).toEqual({
|
||||||
} as any)).toEqual({
|
|
||||||
abc123: { id: 'abc123', autoConnect: false },
|
abc123: { id: 'abc123', autoConnect: false },
|
||||||
def456: { id: 'def456' },
|
def456: { id: 'def456' },
|
||||||
});
|
});
|
||||||
@@ -92,11 +89,10 @@ describe('serverReducer', () => {
|
|||||||
abc123: { ...list.abc123, autoConnect: true },
|
abc123: { ...list.abc123, autoConnect: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(reducer(listWithEnabledAutoConnect, {
|
expect(serversReducer(listWithEnabledAutoConnect, {
|
||||||
type: SET_AUTO_CONNECT,
|
type: setAutoConnect.toString(),
|
||||||
serverId: 'def456',
|
payload: { serverId: 'def456', autoConnect: true },
|
||||||
autoConnect: true,
|
})).toEqual({
|
||||||
} as any)).toEqual({
|
|
||||||
abc123: { id: 'abc123', autoConnect: false },
|
abc123: { id: 'abc123', autoConnect: false },
|
||||||
def456: { id: 'def456', autoConnect: true },
|
def456: { id: 'def456', autoConnect: true },
|
||||||
});
|
});
|
||||||
@@ -104,21 +100,15 @@ describe('serverReducer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('action creators', () => {
|
describe('action creators', () => {
|
||||||
describe('createServer', () => {
|
|
||||||
it('returns expected action', () => {
|
|
||||||
const serverToCreate = Mock.of<RegularServer>({ id: 'abc123' });
|
|
||||||
const result = createServer(serverToCreate);
|
|
||||||
|
|
||||||
expect(result).toEqual(expect.objectContaining({ type: CREATE_SERVERS }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('editServer', () => {
|
describe('editServer', () => {
|
||||||
it('returns expected action', () => {
|
it('returns expected action', () => {
|
||||||
const serverData = { name: 'edited' };
|
const serverData = { name: 'edited' };
|
||||||
const result = editServer('123', serverData);
|
const result = editServer('123', serverData);
|
||||||
|
|
||||||
expect(result).toEqual({ type: EDIT_SERVER, serverId: '123', serverData });
|
expect(result).toEqual({
|
||||||
|
type: editServer.toString(),
|
||||||
|
payload: { serverId: '123', serverData },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,7 +117,10 @@ describe('serverReducer', () => {
|
|||||||
const serverToDelete = Mock.of<RegularServer>({ id: 'abc123' });
|
const serverToDelete = Mock.of<RegularServer>({ id: 'abc123' });
|
||||||
const result = deleteServer(serverToDelete);
|
const result = deleteServer(serverToDelete);
|
||||||
|
|
||||||
expect(result).toEqual({ type: DELETE_SERVER, serverId: 'abc123' });
|
expect(result).toEqual({
|
||||||
|
type: deleteServer.toString(),
|
||||||
|
payload: { id: 'abc123' },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,14 +129,14 @@ describe('serverReducer', () => {
|
|||||||
const newServers = values(list);
|
const newServers = values(list);
|
||||||
const result = createServers(newServers);
|
const result = createServers(newServers);
|
||||||
|
|
||||||
expect(result).toEqual(expect.objectContaining({ type: CREATE_SERVERS }));
|
expect(result).toEqual(expect.objectContaining({ type: createServers.toString() }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates an id for every provided server if they do not have it', () => {
|
it('generates an id for every provided server if they do not have it', () => {
|
||||||
const servers = values(list).map(dissoc('id'));
|
const servers = values(list).map(dissoc('id'));
|
||||||
const { newServers } = createServers(servers);
|
const { payload } = createServers(servers);
|
||||||
|
|
||||||
expect(values(newServers).every(({ id }) => !!id)).toEqual(true);
|
expect(values(payload).every(({ id }) => !!id)).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +148,10 @@ describe('serverReducer', () => {
|
|||||||
const serverToEdit = Mock.of<RegularServer>({ id: 'abc123' });
|
const serverToEdit = Mock.of<RegularServer>({ id: 'abc123' });
|
||||||
const result = setAutoConnect(serverToEdit, autoConnect);
|
const result = setAutoConnect(serverToEdit, autoConnect);
|
||||||
|
|
||||||
expect(result).toEqual({ type: SET_AUTO_CONNECT, serverId: 'abc123', autoConnect });
|
expect(result).toEqual({
|
||||||
|
type: setAutoConnect.toString(),
|
||||||
|
payload: { serverId: 'abc123', autoConnect },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user