Tweaked server types and data

This commit is contained in:
Alejandro Celaya 2020-08-23 10:51:42 +02:00
parent dc78138066
commit 1b7e1e2b5b
8 changed files with 64 additions and 38 deletions

View file

@ -1,11 +1,12 @@
import { MercureInfo } from '../mercure/reducers/mercureInfo'; import { MercureInfo } from '../mercure/reducers/mercureInfo';
import { ServersMap } from '../servers/reducers/servers'; import { ServersMap } from '../servers/reducers/servers';
import { SelectedServer } from '../servers/data';
export type ConnectDecorator = (props: string[], actions?: string[]) => any; export type ConnectDecorator = (props: string[], actions?: string[]) => any;
export interface ShlinkState { export interface ShlinkState {
servers: ServersMap; servers: ServersMap;
selectedServer: any; selectedServer: SelectedServer;
shortUrlsList: any; shortUrlsList: any;
shortUrlsListParams: any; shortUrlsListParams: any;
shortUrlCreationResult: any; shortUrlCreationResult: any;

View file

@ -6,13 +6,13 @@ import NoMenuLayout from '../common/NoMenuLayout';
import { StateFlagTimeout } from '../utils/helpers/hooks'; import { StateFlagTimeout } from '../utils/helpers/hooks';
import { ServerForm } from './helpers/ServerForm'; import { ServerForm } from './helpers/ServerForm';
import { ImportServersBtnProps } from './helpers/ImportServersBtn'; import { ImportServersBtnProps } from './helpers/ImportServersBtn';
import { NewServerData, RegularServer } from './data'; import { NewServerData, ServerWithId } from './data';
import './CreateServer.scss'; import './CreateServer.scss';
const SHOW_IMPORT_MSG_TIME = 4000; const SHOW_IMPORT_MSG_TIME = 4000;
interface CreateServerProps extends RouterProps { interface CreateServerProps extends RouterProps {
createServer: (server: RegularServer) => void; createServer: (server: ServerWithId) => void;
resetSelectedServer: Function; resetSelectedServer: Function;
} }

View file

@ -4,15 +4,23 @@ export interface NewServerData {
apiKey: string; apiKey: string;
} }
export interface RegularServer extends NewServerData { export interface ServerWithId {
id: string; id: string;
version?: string;
printableVersion?: string;
serverNotReachable?: true;
} }
interface NotFoundServer { export interface ReachableServer extends ServerWithId {
version: string;
printableVersion: string;
}
export interface NonReachableServer extends ServerWithId {
serverNotReachable: true;
}
export interface NotFoundServer {
serverNotFound: true; serverNotFound: true;
} }
export type Server = RegularServer | NotFoundServer; export type RegularServer = ReachableServer | NonReachableServer;
export type SelectedServer = RegularServer | NotFoundServer | null;

View file

@ -1,7 +1,7 @@
import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react'; import React, { useRef, RefObject, ChangeEvent, MutableRefObject } from 'react';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import ServersImporter from '../services/ServersImporter'; import ServersImporter from '../services/ServersImporter';
import { Server } from '../data'; import { NewServerData } from '../data';
type Ref<T> = RefObject<T> | MutableRefObject<T>; type Ref<T> = RefObject<T> | MutableRefObject<T>;
@ -11,7 +11,7 @@ export interface ImportServersBtnProps {
} }
interface ImportServersBtnConnectProps extends ImportServersBtnProps { interface ImportServersBtnConnectProps extends ImportServersBtnProps {
createServers: (servers: Server[]) => void; createServers: (servers: NewServerData[]) => void;
fileRef: Ref<HTMLInputElement>; fileRef: Ref<HTMLInputElement>;
} }

View file

@ -1,7 +1,11 @@
import { createAction, handleActions } from 'redux-actions'; import { createAction, handleActions } from 'redux-actions';
import { identity, memoizeWith, pipe } from 'ramda'; import { identity, memoizeWith, pipe } from 'ramda';
import { Action, Dispatch } from 'redux';
import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams'; import { resetShortUrlParams } from '../../short-urls/reducers/shortUrlsListParams';
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../data';
import { GetState } from '../../container/types';
import { ShlinkApiClientBuilder, ShlinkHealth } from '../../utils/services/types';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER'; export const SELECT_SERVER = 'shlink/selectedServer/SELECT_SERVER';
@ -12,22 +16,30 @@ export const MAX_FALLBACK_VERSION = '999.999.999';
export const LATEST_VERSION_CONSTRAINT = 'latest'; export const LATEST_VERSION_CONSTRAINT = 'latest';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
const initialState = null; const initialState: SelectedServer = null;
const versionToSemVer = pipe( const versionToSemVer = pipe(
(version) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version, (version: string) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version,
toSemVer(MIN_FALLBACK_VERSION), toSemVer(MIN_FALLBACK_VERSION),
); );
const getServerVersion = memoizeWith(identity, (serverId, health) => health().then(({ version }) => ({ const getServerVersion = memoizeWith(
identity,
async (_serverId: string, health: () => Promise<ShlinkHealth>) => health().then(({ version }) => ({
version: versionToSemVer(version), version: versionToSemVer(version),
printableVersion: versionToPrintable(version), printableVersion: versionToPrintable(version),
}))); })),
);
export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); export const resetSelectedServer = createAction(RESET_SELECTED_SERVER);
export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId) => async ( export const selectServer = (
dispatch, buildShlinkApiClient: ShlinkApiClientBuilder,
getState, loadMercureInfo: () => Action,
) => (
serverId: string,
) => async (
dispatch: Dispatch,
getState: GetState,
) => { ) => {
dispatch(resetSelectedServer()); dispatch(resetSelectedServer());
dispatch(resetShortUrlParams()); dispatch(resetShortUrlParams());
@ -36,7 +48,7 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId
const selectedServer = servers[serverId]; const selectedServer = servers[serverId];
if (!selectedServer) { if (!selectedServer) {
dispatch({ dispatch<Action & { selectedServer: NotFoundServer }>({
type: SELECT_SERVER, type: SELECT_SERVER,
selectedServer: { serverNotFound: true }, selectedServer: { serverNotFound: true },
}); });
@ -48,7 +60,7 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId
const { health } = buildShlinkApiClient(selectedServer); const { health } = buildShlinkApiClient(selectedServer);
const { version, printableVersion } = await getServerVersion(serverId, health); const { version, printableVersion } = await getServerVersion(serverId, health);
dispatch({ dispatch<Action & { selectedServer: ReachableServer }>({
type: SELECT_SERVER, type: SELECT_SERVER,
selectedServer: { selectedServer: {
...selectedServer, ...selectedServer,
@ -58,14 +70,14 @@ export const selectServer = (buildShlinkApiClient, loadMercureInfo) => (serverId
}); });
dispatch(loadMercureInfo()); dispatch(loadMercureInfo());
} catch (e) { } catch (e) {
dispatch({ dispatch<Action & { selectedServer: NonReachableServer }>({
type: SELECT_SERVER, type: SELECT_SERVER,
selectedServer: { ...selectedServer, serverNotReachable: true }, selectedServer: { ...selectedServer, serverNotReachable: true },
}); });
} }
}; };
export default handleActions({ export default handleActions<SelectedServer, any>({
[RESET_SELECTED_SERVER]: () => initialState, [RESET_SELECTED_SERVER]: () => initialState,
[SELECT_SERVER]: (state, { selectedServer }) => selectedServer, [SELECT_SERVER]: (_, { selectedServer }: any) => selectedServer,
}, initialState); }, initialState);

View file

@ -1,7 +1,7 @@
import { handleActions } from 'redux-actions'; import { handleActions } from 'redux-actions';
import { pipe, assoc, map, reduce, dissoc } from 'ramda'; import { pipe, assoc, map, reduce, dissoc } from 'ramda';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { RegularServer, NewServerData } from '../data'; import { NewServerData, ServerWithId } from '../data';
/* eslint-disable padding-line-between-statements */ /* eslint-disable padding-line-between-statements */
export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER'; export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER';
@ -9,13 +9,13 @@ export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER';
export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS'; export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
export type ServersMap = Record<string, RegularServer>; export type ServersMap = Record<string, ServerWithId>;
const initialState: ServersMap = {}; const initialState: ServersMap = {};
const serverWithId = (server: RegularServer | NewServerData): RegularServer => { const serverWithId = (server: ServerWithId | NewServerData): ServerWithId => {
if ((server as RegularServer).id) { if ((server as ServerWithId).id) {
return server as RegularServer; return server as ServerWithId;
} }
return assoc('id', uuid(), server); return assoc('id', uuid(), server);
@ -29,7 +29,7 @@ export default handleActions<ServersMap, any>({
: assoc(serverId, { ...state[serverId], ...serverData }, state), : assoc(serverId, { ...state[serverId], ...serverData }, state),
}, initialState); }, initialState);
const serversListToMap = reduce<RegularServer, 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 createServers = pipe(
map(serverWithId), map(serverWithId),
@ -37,7 +37,7 @@ export const createServers = pipe(
(newServers: ServersMap) => ({ type: CREATE_SERVERS, newServers }), (newServers: ServersMap) => ({ type: CREATE_SERVERS, newServers }),
); );
export const createServer = (server: RegularServer) => createServers([ server ]); export const createServer = (server: ServerWithId) => createServers([ server ]);
export const editServer = (serverId: string, serverData: Partial<NewServerData>) => ({ export const editServer = (serverId: string, serverData: Partial<NewServerData>) => ({
type: EDIT_SERVER, type: EDIT_SERVER,
@ -45,4 +45,4 @@ export const editServer = (serverId: string, serverData: Partial<NewServerData>)
serverData, serverData,
}); });
export const deleteServer = ({ id }: RegularServer) => ({ type: DELETE_SERVER, serverId: id }); export const deleteServer = ({ id }: ServerWithId) => ({ type: DELETE_SERVER, serverId: id });

View file

@ -1,12 +1,12 @@
import { CsvJson } from 'csvjson'; import { CsvJson } from 'csvjson';
import { RegularServer } from '../data'; import { NewServerData } from '../data';
const CSV_MIME_TYPE = 'text/csv'; const CSV_MIME_TYPE = 'text/csv';
export default class ServersImporter { export default class ServersImporter {
public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {}
public importServersFromFile = async (file?: File | null): Promise<RegularServer[]> => { public importServersFromFile = async (file?: File | null): Promise<NewServerData[]> => {
if (!file || file.type !== CSV_MIME_TYPE) { if (!file || file.type !== CSV_MIME_TYPE) {
throw new Error('No file provided or file is not a CSV'); throw new Error('No file provided or file is not a CSV');
} }
@ -16,7 +16,7 @@ export default class ServersImporter {
return new Promise((resolve) => { return new Promise((resolve) => {
reader.addEventListener('loadend', (e: ProgressEvent<FileReader>) => { reader.addEventListener('loadend', (e: ProgressEvent<FileReader>) => {
const content = e.target?.result?.toString() ?? ''; const content = e.target?.result?.toString() ?? '';
const servers = this.csvjson.toObject<RegularServer>(content); const servers = this.csvjson.toObject<NewServerData>(content);
resolve(servers); resolve(servers);
}); });

View file

@ -1,11 +1,16 @@
import { RegularServer } from '../../servers/data'; import { ServerWithId } from '../../servers/data';
import { GetState } from '../../container/types'; import { GetState } from '../../container/types';
import ShlinkApiClient from './ShlinkApiClient'; import ShlinkApiClient from './ShlinkApiClient';
// FIXME Move to ShlinkApiClientBuilder // FIXME Move to ShlinkApiClientBuilder
export type ShlinkApiClientBuilder = (getStateOrSelectedServer: RegularServer | GetState) => ShlinkApiClient; export type ShlinkApiClientBuilder = (getStateOrSelectedServer: ServerWithId | GetState) => ShlinkApiClient;
export interface ShlinkMercureInfo { export interface ShlinkMercureInfo {
token: string; token: string;
mercureHubUrl: string; mercureHubUrl: string;
} }
export interface ShlinkHealth {
status: 'pass' | 'fail';
version: string;
}