Bladeren bron

Schema registry. Index page

Guzel Kafizova 4 jaren geleden
bovenliggende
commit
00613bd4fc

+ 7 - 2
kafka-ui-react-app/src/components/App.tsx

@@ -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"

+ 8 - 0
kafka-ui-react-app/src/components/Nav/ClusterMenu.tsx

@@ -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>

+ 0 - 0
kafka-ui-react-app/src/components/Nav/NavConatiner.ts → kafka-ui-react-app/src/components/Nav/NavContainer.ts


+ 32 - 0
kafka-ui-react-app/src/components/Schemas/List/List.tsx

@@ -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;

+ 11 - 0
kafka-ui-react-app/src/components/Schemas/List/ListContainer.tsx

@@ -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));

+ 15 - 0
kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx

@@ -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;

+ 41 - 0
kafka-ui-react-app/src/components/Schemas/Schemas.tsx

@@ -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;

+ 15 - 0
kafka-ui-react-app/src/components/Schemas/SchemasContainer.tsx

@@ -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);

+ 2 - 0
kafka-ui-react-app/src/lib/paths.ts

@@ -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,

+ 4 - 0
kafka-ui-react-app/src/redux/actionType.ts

@@ -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;

+ 7 - 1
kafka-ui-react-app/src/redux/actions/actions.ts

@@ -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>();

+ 16 - 1
kafka-ui-react-app/src/redux/actions/thunks.ts

@@ -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());
+  }
+};

+ 3 - 0
kafka-ui-react-app/src/redux/interfaces/index.ts

@@ -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;
 }
 

+ 10 - 0
kafka-ui-react-app/src/redux/interfaces/schema.ts

@@ -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[];
+}

+ 2 - 0
kafka-ui-react-app/src/redux/reducers/index.ts

@@ -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,
 });

+ 43 - 0
kafka-ui-react-app/src/redux/reducers/schemas/reducer.ts

@@ -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;

+ 29 - 0
kafka-ui-react-app/src/redux/reducers/schemas/selectors.ts

@@ -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]);
+  }
+);