Schema show page (#196)
* Latest version added * Changes from [PR-186] applied * Schema Versions get method added * Schema show page created * Updated JSONViewer * Details component refactored * Tests for Messages component updated * Details component and packages updated * Feedback * Delete testSchema Co-authored-by: Oleg Shuralev <workshur@gmail.com>
This commit is contained in:
parent
2cf2f6e186
commit
44949b9af2
23 changed files with 358 additions and 81 deletions
|
@ -30,6 +30,8 @@ services:
|
|||
environment:
|
||||
ZOOKEEPER_CLIENT_PORT: 2181
|
||||
ZOOKEEPER_TICK_TIME: 2000
|
||||
ports:
|
||||
- 2181:2181
|
||||
|
||||
kafka0:
|
||||
image: confluentinc/cp-kafka:5.1.0
|
||||
|
|
6
kafka-ui-react-app/package-lock.json
generated
6
kafka-ui-react-app/package-lock.json
generated
|
@ -4962,9 +4962,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"bulma": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.8.2.tgz",
|
||||
"integrity": "sha512-vMM/ijYSxX+Sm+nD7Lmc1UgWDy2JcL2nTKqwgEqXuOMU+IGALbXd5MLt/BcjBAPLIx36TtzhzBcSnOP974gcqA=="
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.2.tgz",
|
||||
"integrity": "sha512-e14EF+3VSZ488yL/lJH0tR8mFWiEQVCMi/BQUMi2TGMBOk+zrDg4wryuwm/+dRSHJw0gMawp2tsW7X1JYUCE3A=="
|
||||
},
|
||||
"bulma-switch": {
|
||||
"version": "2.0.0",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"bulma": "^0.8.2",
|
||||
"bulma": "^0.9.2",
|
||||
"bulma-switch": "^2.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^2.16.1",
|
||||
|
@ -112,6 +112,8 @@
|
|||
},
|
||||
"proxy": "http://localhost:8080",
|
||||
"jest": {
|
||||
"snapshotSerializers": ["enzyme-to-json/serializer"]
|
||||
"snapshotSerializers": [
|
||||
"enzyme-to-json/serializer"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
110
kafka-ui-react-app/src/components/Schemas/Details/Details.tsx
Normal file
110
kafka-ui-react-app/src/components/Schemas/Details/Details.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import { ClusterName, SchemaName } from 'redux/interfaces';
|
||||
import { clusterSchemasPath } from 'lib/paths';
|
||||
import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
|
||||
import SchemaVersion from './SchemaVersion';
|
||||
import LatestVersionItem from './LatestVersionItem';
|
||||
import PageLoader from '../../common/PageLoader/PageLoader';
|
||||
|
||||
interface DetailsProps {
|
||||
schema: SchemaSubject;
|
||||
clusterName: ClusterName;
|
||||
versions: SchemaSubject[];
|
||||
isFetched: boolean;
|
||||
fetchSchemaVersions: (
|
||||
clusterName: ClusterName,
|
||||
schemaName: SchemaName
|
||||
) => void;
|
||||
}
|
||||
|
||||
const Details: React.FC<DetailsProps> = ({
|
||||
schema,
|
||||
clusterName,
|
||||
fetchSchemaVersions,
|
||||
versions,
|
||||
isFetched,
|
||||
}) => {
|
||||
React.useEffect(() => {
|
||||
fetchSchemaVersions(clusterName, schema.subject as SchemaName);
|
||||
}, [fetchSchemaVersions, clusterName]);
|
||||
return (
|
||||
<div className="section">
|
||||
<div className="level">
|
||||
<Breadcrumb
|
||||
links={[
|
||||
{
|
||||
href: clusterSchemasPath(clusterName),
|
||||
label: 'Schema Registry',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{schema.subject}
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div className="box">
|
||||
<div className="level">
|
||||
<div className="level-left">
|
||||
<div className="level-item">
|
||||
<div className="mr-1">
|
||||
<b>Latest Version</b>
|
||||
</div>
|
||||
<div className="tag is-info is-light" title="Version">
|
||||
#{schema.version}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="level-right">
|
||||
<button
|
||||
className="button is-primary is-small level-item"
|
||||
type="button"
|
||||
title="in development"
|
||||
disabled
|
||||
>
|
||||
Create Schema
|
||||
</button>
|
||||
<button
|
||||
className="button is-warning is-small level-item"
|
||||
type="button"
|
||||
title="in development"
|
||||
disabled
|
||||
>
|
||||
Update Schema
|
||||
</button>
|
||||
<button
|
||||
className="button is-danger is-small level-item"
|
||||
type="button"
|
||||
title="in development"
|
||||
disabled
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<LatestVersionItem schema={schema} />
|
||||
</div>
|
||||
{isFetched ? (
|
||||
<div className="box">
|
||||
<table className="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>ID</th>
|
||||
<th>Schema</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{versions.map((version) => (
|
||||
<SchemaVersion key={version.id} version={version} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<PageLoader />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Details;
|
|
@ -0,0 +1,39 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { ClusterName, RootState } from 'redux/interfaces';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import {
|
||||
getIsSchemaVersionFetched,
|
||||
getSchema,
|
||||
getSortedSchemaVersions,
|
||||
} from 'redux/reducers/schemas/selectors';
|
||||
import { fetchSchemaVersions } from 'redux/actions';
|
||||
import Details from './Details';
|
||||
|
||||
interface RouteProps {
|
||||
clusterName: ClusterName;
|
||||
subject: string;
|
||||
}
|
||||
|
||||
type OwnProps = RouteComponentProps<RouteProps>;
|
||||
|
||||
const mapStateToProps = (
|
||||
state: RootState,
|
||||
{
|
||||
match: {
|
||||
params: { clusterName, subject },
|
||||
},
|
||||
}: OwnProps
|
||||
) => ({
|
||||
schema: getSchema(state, subject),
|
||||
versions: getSortedSchemaVersions(state),
|
||||
isFetched: getIsSchemaVersionFetched(state),
|
||||
clusterName,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchSchemaVersions,
|
||||
};
|
||||
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Details)
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import JSONViewer from 'components/common/JSONViewer/JSONViewer';
|
||||
|
||||
interface LatestVersionProps {
|
||||
schema: SchemaSubject;
|
||||
}
|
||||
|
||||
const LatestVersionItem: React.FC<LatestVersionProps> = ({
|
||||
schema: { id, subject, schema, compatibilityLevel },
|
||||
}) => {
|
||||
return (
|
||||
<div className="tile is-ancestor mt-1">
|
||||
<div className="tile is-4 is-parent">
|
||||
<div className="tile is-child">
|
||||
<table className="table is-fullwidth">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>{id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subject</td>
|
||||
<td>{subject}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Compatibility</td>
|
||||
<td>{compatibilityLevel}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tile is-parent">
|
||||
<div className="tile is-child box py-1">
|
||||
<JSONViewer data={JSON.parse(schema as string)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatestVersionItem;
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import JSONViewer from 'components/common/JSONViewer/JSONViewer';
|
||||
|
||||
interface SchemaVersionProps {
|
||||
version: SchemaSubject;
|
||||
}
|
||||
|
||||
const SchemaVersion: React.FC<SchemaVersionProps> = ({
|
||||
version: { version, id, schema },
|
||||
}) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{version}</td>
|
||||
<td>{id}</td>
|
||||
<td className="py-0">
|
||||
<JSONViewer data={JSON.parse(schema as string)} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default SchemaVersion;
|
|
@ -1,5 +1,5 @@
|
|||
import { SchemaSubject } from 'generated-sources';
|
||||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
|
@ -16,11 +16,13 @@ const List: React.FC<ListProps> = ({ schemas }) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Schema Name</th>
|
||||
<th>Version</th>
|
||||
<th>Compatibility</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{schemas.map(({ subject }) => (
|
||||
<ListItem subject={subject} />
|
||||
{schemas.map((subject) => (
|
||||
<ListItem key={subject.id} subject={subject} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
import React from 'react';
|
||||
import { SchemaSubject } from 'generated-sources';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
interface ListItemProps {
|
||||
subject?: string;
|
||||
subject: SchemaSubject;
|
||||
}
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ subject }) => {
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
subject: { subject, version, compatibilityLevel },
|
||||
}) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{subject}</td>
|
||||
<td>
|
||||
<NavLink
|
||||
exact
|
||||
to={`schemas/${subject}/latest`}
|
||||
activeClassName="is-active"
|
||||
className="title is-6"
|
||||
>
|
||||
{subject}
|
||||
</NavLink>
|
||||
</td>
|
||||
<td>{version}</td>
|
||||
<td>
|
||||
<span className="tag is-link">{compatibilityLevel}</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ 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';
|
||||
import DetailsContainer from './Details/DetailsContainer';
|
||||
|
||||
interface SchemasProps {
|
||||
isFetched: boolean;
|
||||
|
@ -31,6 +32,11 @@ const Schemas: React.FC<SchemasProps> = ({
|
|||
path="/ui/clusters/:clusterName/schemas"
|
||||
component={ListContainer}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/schemas/:subject/latest"
|
||||
component={DetailsContainer}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from '../../redux/interfaces';
|
||||
import { fetchSchemasByClusterName } from '../../redux/actions';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { fetchSchemasByClusterName } from 'redux/actions';
|
||||
import { getIsSchemaListFetched } from 'redux/reducers/schemas/selectors';
|
||||
import Schemas from './Schemas';
|
||||
import { getIsSchemaListFetched } from '../../redux/reducers/schemas/selectors';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isFetched: getIsSchemaListFetched(state),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { format } from 'date-fns';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import { TopicMessage } from 'generated-sources';
|
||||
import JSONViewer from 'components/common/JSONViewer/JSONViewer';
|
||||
|
||||
export interface MessageItemProp {
|
||||
partition: TopicMessage['partition'];
|
||||
|
@ -21,28 +21,7 @@ const MessageItem: React.FC<MessageItemProp> = ({
|
|||
<td style={{ width: 150 }}>{offset}</td>
|
||||
<td style={{ width: 100 }}>{partition}</td>
|
||||
<td style={{ wordBreak: 'break-word' }}>
|
||||
{content && (
|
||||
<JSONTree
|
||||
data={content}
|
||||
hideRoot
|
||||
invertTheme={false}
|
||||
theme={{
|
||||
tree: ({ style }) => ({
|
||||
style: {
|
||||
...style,
|
||||
backgroundColor: undefined,
|
||||
marginLeft: 0,
|
||||
marginTop: 0,
|
||||
},
|
||||
}),
|
||||
value: ({ style }) => ({
|
||||
style: { ...style, marginLeft: 0 },
|
||||
}),
|
||||
base0D: '#3273dc',
|
||||
base0B: '#363636',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{content && <JSONViewer data={content as { [key: string]: string }} />}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ const MessagesTable: React.FC<MessagesTableProp> = ({ messages, onNext }) => {
|
|||
partition={partition}
|
||||
offset={offset}
|
||||
timestamp={timestamp}
|
||||
content={content as Record<string, unknown>}
|
||||
content={content as { [key: string]: string }}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('MessageItem', () => {
|
|||
|
||||
expect(wrapper.find('tr').length).toEqual(1);
|
||||
expect(wrapper.find('td').length).toEqual(4);
|
||||
expect(wrapper.find('JSONTree').length).toEqual(1);
|
||||
expect(wrapper.find('JSONViewer').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('matches snapshot', () => {
|
||||
|
@ -28,7 +28,7 @@ describe('MessageItem', () => {
|
|||
|
||||
expect(wrapper.find('tr').length).toEqual(1);
|
||||
expect(wrapper.find('td').length).toEqual(4);
|
||||
expect(wrapper.find('JSONTree').length).toEqual(0);
|
||||
expect(wrapper.find('JSONViewer').length).toEqual(0);
|
||||
});
|
||||
|
||||
it('matches snapshot', () => {
|
||||
|
|
|
@ -36,35 +36,13 @@ exports[`MessageItem when content is defined matches snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
>
|
||||
<JSONTree
|
||||
collectionLimit={50}
|
||||
<JSONViewer
|
||||
data={
|
||||
Object {
|
||||
"foo": "bar",
|
||||
"key": "val",
|
||||
}
|
||||
}
|
||||
getItemString={[Function]}
|
||||
hideRoot={true}
|
||||
invertTheme={false}
|
||||
isCustomNode={[Function]}
|
||||
keyPath={
|
||||
Array [
|
||||
"root",
|
||||
]
|
||||
}
|
||||
labelRenderer={[Function]}
|
||||
postprocessValue={[Function]}
|
||||
shouldExpandNode={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"base0B": "#363636",
|
||||
"base0D": "#3273dc",
|
||||
"tree": [Function],
|
||||
"value": [Function],
|
||||
}
|
||||
}
|
||||
valueRenderer={[Function]}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import theme from './themes/google';
|
||||
|
||||
interface JSONViewerProps {
|
||||
data: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
const JSONViewer: React.FC<JSONViewerProps> = ({ data }) => (
|
||||
<JSONTree data={data} theme={theme} shouldExpandNode={() => true} hideRoot />
|
||||
);
|
||||
|
||||
export default JSONViewer;
|
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
scheme: 'google',
|
||||
author: 'seth wright (http://sethawright.com)',
|
||||
base00: '#1d1f21',
|
||||
base01: '#282a2e',
|
||||
base02: '#373b41',
|
||||
base03: '#969896',
|
||||
base04: '#b4b7b4',
|
||||
base05: '#c5c8c6',
|
||||
base06: '#e0e0e0',
|
||||
base07: '#ffffff',
|
||||
base08: '#CC342B',
|
||||
base09: '#F96A38',
|
||||
base0A: '#FBA922',
|
||||
base0B: '#198844',
|
||||
base0C: '#3971ED',
|
||||
base0D: '#3971ED',
|
||||
base0E: '#A36AC7',
|
||||
base0F: '#3971ED',
|
||||
};
|
|
@ -103,3 +103,9 @@ export const fetchSchemasByClusterNameAction = createAsyncAction(
|
|||
'GET_CLUSTER_SCHEMAS__SUCCESS',
|
||||
'GET_CLUSTER_SCHEMAS__FAILURE'
|
||||
)<undefined, SchemaSubject[], undefined>();
|
||||
|
||||
export const fetchSchemaVersionsAction = createAsyncAction(
|
||||
'GET_SCHEMA_VERSIONS__REQUEST',
|
||||
'GET_SCHEMA_VERSIONS__SUCCESS',
|
||||
'GET_SCHEMA_VERSIONS__FAILURE'
|
||||
)<undefined, SchemaSubject[], undefined>();
|
||||
|
|
|
@ -5,7 +5,6 @@ import {
|
|||
Topic,
|
||||
TopicFormData,
|
||||
TopicConfig,
|
||||
SchemaSubject,
|
||||
} from 'generated-sources';
|
||||
import {
|
||||
ConsumerGroupID,
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
TopicMessageQueryParams,
|
||||
TopicFormFormattedParams,
|
||||
TopicFormDataRaw,
|
||||
SchemaName,
|
||||
} from 'redux/interfaces';
|
||||
|
||||
import { BASE_PARAMS } from 'lib/constants';
|
||||
|
@ -257,17 +257,26 @@ export const fetchSchemasByClusterName = (
|
|||
): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.request());
|
||||
try {
|
||||
const schemaNames = await apiClient.getSchemas({ clusterName });
|
||||
|
||||
// TODO: Remove me after API refactoring
|
||||
const schemas: SchemaSubject[] = await Promise.all(
|
||||
schemaNames.map((schemaName) =>
|
||||
apiClient.getLatestSchema({ clusterName, schemaName })
|
||||
)
|
||||
);
|
||||
|
||||
const schemas = await apiClient.getSchemas({ clusterName });
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.success(schemas));
|
||||
} catch (e) {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.failure());
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchSchemaVersions = (
|
||||
clusterName: ClusterName,
|
||||
subject: SchemaName
|
||||
): PromiseThunk<void> => async (dispatch) => {
|
||||
if (!subject) return;
|
||||
dispatch(actions.fetchSchemaVersionsAction.request());
|
||||
try {
|
||||
const versions = await apiClient.getAllVersionsBySubject({
|
||||
clusterName,
|
||||
subject,
|
||||
});
|
||||
dispatch(actions.fetchSchemaVersionsAction.success(versions));
|
||||
} catch (e) {
|
||||
dispatch(actions.fetchSchemaVersionsAction.failure());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { SchemaSubject } from 'generated-sources';
|
||||
|
||||
export type SchemaName = string;
|
||||
|
||||
export interface SchemasState {
|
||||
byName: { [name: string]: SchemaSubject };
|
||||
allNames: string[];
|
||||
byName: { [subject: string]: SchemaSubject };
|
||||
allNames: SchemaName[];
|
||||
currentSchemaVersions: SchemaSubject[];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Action, SchemasState } from 'redux/interfaces';
|
|||
export const initialState: SchemasState = {
|
||||
byName: {},
|
||||
allNames: [],
|
||||
currentSchemaVersions: [],
|
||||
};
|
||||
|
||||
const updateSchemaList = (
|
||||
|
@ -35,6 +36,8 @@ const reducer = (state = initialState, action: Action): SchemasState => {
|
|||
switch (action.type) {
|
||||
case 'GET_CLUSTER_SCHEMAS__SUCCESS':
|
||||
return updateSchemaList(state, action.payload);
|
||||
case 'GET_SCHEMA_VERSIONS__SUCCESS':
|
||||
return { ...state, currentSchemaVersions: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -11,19 +11,38 @@ const getSchemaListFetchingStatus = createFetchingSelector(
|
|||
'GET_CLUSTER_SCHEMAS'
|
||||
);
|
||||
|
||||
const getSchemaVersionsFetchingStatus = createFetchingSelector(
|
||||
'GET_SCHEMA_VERSIONS'
|
||||
);
|
||||
|
||||
export const getIsSchemaListFetched = createSelector(
|
||||
getSchemaListFetchingStatus,
|
||||
(status) => status === 'fetched'
|
||||
);
|
||||
|
||||
export const getIsSchemaVersionFetched = createSelector(
|
||||
getSchemaVersionsFetchingStatus,
|
||||
(status) => status === 'fetched'
|
||||
);
|
||||
|
||||
export const getSchemaList = createSelector(
|
||||
getIsSchemaListFetched,
|
||||
getAllNames,
|
||||
getSchemaMap,
|
||||
(isFetched, allNames, byName) => {
|
||||
if (!isFetched) {
|
||||
return [];
|
||||
}
|
||||
return allNames.map((subject) => byName[subject]);
|
||||
}
|
||||
(isFetched, allNames, byName) =>
|
||||
isFetched ? allNames.map((subject) => byName[subject]) : []
|
||||
);
|
||||
|
||||
const getSchemaName = (_: RootState, subject: string) => subject;
|
||||
|
||||
export const getSchema = createSelector(
|
||||
getSchemaMap,
|
||||
getSchemaName,
|
||||
(schemas, subject) => schemas[subject]
|
||||
);
|
||||
|
||||
export const getSortedSchemaVersions = createSelector(
|
||||
schemasState,
|
||||
({ currentSchemaVersions }) =>
|
||||
currentSchemaVersions.sort((a, b) => a.id - b.id)
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import "../../node_modules/bulma/sass/base/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/form/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/helpers/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/components/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/grid/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/layout/_all.sass";
|
||||
|
|
Loading…
Add table
Reference in a new issue