Schema registry. Index page

This commit is contained in:
Guzel Kafizova 2021-02-10 13:54:05 +03:00
parent 40d85643bb
commit 00613bd4fc
17 changed files with 245 additions and 4 deletions

View file

@ -3,10 +3,11 @@ import { Switch, Route, Redirect } from 'react-router-dom';
import './App.scss';
import BrokersContainer from './Brokers/BrokersContainer';
import TopicsContainer from './Topics/TopicsContainer';
import NavConatiner from './Nav/NavConatiner';
import NavContainer from './Nav/NavContainer';
import PageLoader from './common/PageLoader/PageLoader';
import Dashboard from './Dashboard/Dashboard';
import ConsumersGroupsContainer from './ConsumerGroups/ConsumersGroupsContainer';
import SchemasContainer from './Schemas/SchemasContainer';
interface AppProps {
isClusterListFetched: boolean;
@ -35,7 +36,7 @@ const App: React.FC<AppProps> = ({
</div>
</nav>
<main className="Layout__container">
<NavConatiner className="Layout__navbar" />
<NavContainer className="Layout__navbar" />
{isClusterListFetched ? (
<Switch>
<Route
@ -55,6 +56,10 @@ const App: React.FC<AppProps> = ({
path="/ui/clusters/:clusterName/consumer-groups"
component={ConsumersGroupsContainer}
/>
<Route
path="/ui/clusters/:clusterName/schemas"
component={SchemasContainer}
/>
<Redirect
from="/ui/clusters/:clusterName"
to="/ui/clusters/:clusterName/brokers"

View file

@ -4,6 +4,7 @@ import {
clusterBrokersPath,
clusterTopicsPath,
clusterConsumerGroupsPath,
clusterSchemasPath,
} from 'lib/paths';
import { Cluster, ServerStatus } from 'generated-sources';
@ -85,6 +86,13 @@ const ClusterMenu: React.FC<Props> = ({ cluster }) => (
>
Consumers
</NavLink>
<NavLink
to={clusterSchemasPath(cluster.name)}
activeClassName="is-active"
title="Schema Registry"
>
Schema Registry
</NavLink>
</ul>
</li>
</ul>

View file

@ -0,0 +1,32 @@
import React from 'react';
import { Schema } from 'redux/interfaces';
import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
import ListItem from './ListItem';
interface ListProps {
schemas: Schema[];
}
const List: React.FC<ListProps> = ({ schemas }) => {
return (
<div className="section">
<Breadcrumb>Schema Registry</Breadcrumb>
<div className="box">
<table className="table is-striped is-fullwidth">
<thead>
<tr>
<th>Schema Name</th>
</tr>
</thead>
<tbody>
{schemas.map(({ name }) => (
<ListItem schemaName={name} />
))}
</tbody>
</table>
</div>
</div>
);
};
export default List;

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import { RootState } from 'redux/interfaces';
import { withRouter } from 'react-router-dom';
import { getSchemaList } from 'redux/reducers/schemas/selectors';
import List from './List';
const mapStateToProps = (state: RootState) => ({
schemas: getSchemaList(state),
});
export default withRouter(connect(mapStateToProps)(List));

View file

@ -0,0 +1,15 @@
import React from 'react';
interface ListItemProps {
schemaName: string;
}
const ListItem: React.FC<ListItemProps> = ({ schemaName }) => {
return (
<tr>
<td>{schemaName}</td>
</tr>
);
};
export default ListItem;

View file

@ -0,0 +1,41 @@
import React from 'react';
import { ClusterName } from 'redux/interfaces';
import { Switch, Route, useParams } from 'react-router-dom';
import PageLoader from 'components/common/PageLoader/PageLoader';
import ListContainer from './List/ListContainer';
interface SchemasProps {
isFetched: boolean;
fetchSchemasByClusterName: (clusterName: ClusterName) => void;
}
interface ParamTypes {
clusterName: string;
}
const Schemas: React.FC<SchemasProps> = ({
isFetched,
fetchSchemasByClusterName,
}) => {
const { clusterName } = useParams<ParamTypes>();
React.useEffect(() => {
fetchSchemasByClusterName(clusterName);
}, [fetchSchemasByClusterName, clusterName]);
if (isFetched) {
return (
<Switch>
<Route
exact
path="/ui/clusters/:clusterName/schemas"
component={ListContainer}
/>
</Switch>
);
}
return <PageLoader />;
};
export default Schemas;

View file

@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import { RootState } from '../../redux/interfaces';
import { fetchSchemasByClusterName } from '../../redux/actions';
import Schemas from './Schemas';
import { getIsSchemaListFetched } from '../../redux/reducers/schemas/selectors';
const mapStateToProps = (state: RootState) => ({
isFetched: getIsSchemaListFetched(state),
});
const mapDispatchToProps = {
fetchSchemasByClusterName,
};
export default connect(mapStateToProps, mapDispatchToProps)(Schemas);

View file

@ -10,6 +10,8 @@ export const clusterTopicNewPath = (clusterName: ClusterName) =>
`${clusterPath(clusterName)}/topics/new`;
export const clusterConsumerGroupsPath = (clusterName: ClusterName) =>
`${clusterPath(clusterName)}/consumer-groups`;
export const clusterSchemasPath = (clusterName: ClusterName) =>
`${clusterPath(clusterName)}/schemas`;
export const clusterTopicPath = (
clusterName: ClusterName,

View file

@ -50,6 +50,10 @@ enum ActionType {
GET_CONSUMER_GROUP_DETAILS__REQUEST = 'GET_CONSUMER_GROUP_DETAILS__REQUEST',
GET_CONSUMER_GROUP_DETAILS__SUCCESS = 'GET_CONSUMER_GROUP_DETAILS__SUCCESS',
GET_CONSUMER_GROUP_DETAILS__FAILURE = 'GET_CONSUMER_GROUP_DETAILS__FAILURE',
GET_CLUSTER_SCHEMAS__REQUEST = 'GET_CLUSTER_SCHEMAS__REQUEST',
GET_CLUSTER_SCHEMAS__SUCCESS = 'GET_CLUSTER_SCHEMAS__SUCCESS',
GET_CLUSTER_SCHEMAS__FAILURE = 'GET_CLUSTER_SCHEMAS__FAILURE',
}
export default ActionType;

View file

@ -1,6 +1,6 @@
import { createAsyncAction } from 'typesafe-actions';
import ActionType from 'redux/actionType';
import { TopicName, ConsumerGroupID } from 'redux/interfaces';
import { TopicName, ConsumerGroupID, Schema } from 'redux/interfaces';
import {
Cluster,
@ -97,3 +97,9 @@ export const fetchConsumerGroupDetailsAction = createAsyncAction(
{ consumerGroupID: ConsumerGroupID; details: ConsumerGroupDetails },
undefined
>();
export const fetchSchemasByClusterNameAction = createAsyncAction(
ActionType.GET_CLUSTER_SCHEMAS__REQUEST,
ActionType.GET_CLUSTER_SCHEMAS__SUCCESS,
ActionType.GET_CLUSTER_SCHEMAS__FAILURE
)<undefined, Schema[], undefined>();

View file

@ -15,13 +15,14 @@ import {
TopicMessageQueryParams,
TopicFormFormattedParams,
TopicFormDataRaw,
Schema,
} from 'redux/interfaces';
import { BASE_PARAMS } from 'lib/constants';
import * as actions from './actions';
const apiClientConf = new Configuration(BASE_PARAMS);
const apiClient = new ApiClustersApi(apiClientConf);
export const apiClient = new ApiClustersApi(apiClientConf);
export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchClusterListAction.request());
@ -250,3 +251,17 @@ export const fetchConsumerGroupDetails = (
dispatch(actions.fetchConsumerGroupDetailsAction.failure());
}
};
export const fetchSchemasByClusterName = (
clusterName: ClusterName
): PromiseThunk<void> => async (dispatch) => {
dispatch(actions.fetchSchemasByClusterNameAction.request());
try {
const schemaNames = await apiClient.getSchemas({ clusterName });
const schemas: Schema[] = schemaNames.map((name) => ({ name }));
dispatch(actions.fetchSchemasByClusterNameAction.success(schemas));
} catch (e) {
dispatch(actions.fetchSchemasByClusterNameAction.failure());
}
};

View file

@ -9,11 +9,13 @@ import { ClusterState } from './cluster';
import { BrokersState } from './broker';
import { LoaderState } from './loader';
import { ConsumerGroupsState } from './consumerGroup';
import { SchemasState } from './schema';
export * from './topic';
export * from './cluster';
export * from './broker';
export * from './consumerGroup';
export * from './schema';
export * from './loader';
export enum FetchStatus {
@ -28,6 +30,7 @@ export interface RootState {
clusters: ClusterState;
brokers: BrokersState;
consumerGroups: ConsumerGroupsState;
schemas: SchemasState;
loader: LoaderState;
}

View file

@ -0,0 +1,10 @@
import { SchemaSubject } from 'generated-sources';
export interface Schema extends Partial<SchemaSubject> {
name: string;
}
export interface SchemasState {
byName: { [name: string]: Schema };
allNames: string[];
}

View file

@ -4,6 +4,7 @@ import topics from './topics/reducer';
import clusters from './clusters/reducer';
import brokers from './brokers/reducer';
import consumerGroups from './consumerGroups/reducer';
import schemas from './schemas/reducer';
import loader from './loader/reducer';
export default combineReducers<RootState>({
@ -11,5 +12,6 @@ export default combineReducers<RootState>({
clusters,
brokers,
consumerGroups,
schemas,
loader,
});

View file

@ -0,0 +1,43 @@
import ActionType from 'redux/actionType';
import { Action, Schema, SchemasState } from 'redux/interfaces';
export const initialState: SchemasState = {
byName: {},
allNames: [],
};
const updateSchemaList = (
state: SchemasState,
payload: Schema[]
): SchemasState => {
const initialMemo: SchemasState = {
...state,
allNames: [],
};
return payload.reduce(
(memo: SchemasState, schema) => ({
...memo,
byName: {
...memo.byName,
[schema.name]: {
...memo.byName[schema.name],
name: schema.name,
},
},
allNames: [...memo.allNames, schema.name],
}),
initialMemo
);
};
const reducer = (state = initialState, action: Action): SchemasState => {
switch (action.type) {
case ActionType.GET_CLUSTER_SCHEMAS__SUCCESS:
return updateSchemaList(state, action.payload);
default:
return state;
}
};
export default reducer;

View file

@ -0,0 +1,29 @@
import { createSelector } from 'reselect';
import { FetchStatus, RootState, SchemasState } from 'redux/interfaces';
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
const schemasState = ({ schemas }: RootState): SchemasState => schemas;
const getAllNames = (state: RootState) => schemasState(state).allNames;
const getSchemaMap = (state: RootState) => schemasState(state).byName;
const getSchemaListFetchingStatus = createFetchingSelector(
'GET_CLUSTER_SCHEMAS'
);
export const getIsSchemaListFetched = createSelector(
getSchemaListFetchingStatus,
(status) => status === FetchStatus.fetched
);
export const getSchemaList = createSelector(
getIsSchemaListFetched,
getAllNames,
getSchemaMap,
(isFetched, allNames, byName) => {
if (!isFetched) {
return [];
}
return allNames.map((schemaName) => byName[schemaName]);
}
);