mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-13 19:33:55 +03:00
GT-721-change-schema-regsitry-tab-position
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10335 GitOrigin-RevId: da2e2f53f85477b80e90dd8179ffde28108679f4
This commit is contained in:
parent
57b471ef03
commit
f386414058
@ -3,6 +3,9 @@ import { Link, RouteComponentProps } from 'react-router';
|
|||||||
import { isProConsole } from '../../../utils/proConsole';
|
import { isProConsole } from '../../../utils/proConsole';
|
||||||
import { useEELiteAccess } from '../../../features/EETrial';
|
import { useEELiteAccess } from '../../../features/EETrial';
|
||||||
import globals from '../../../Globals';
|
import globals from '../../../Globals';
|
||||||
|
import { IconTooltip } from '../../../new-components/Tooltip';
|
||||||
|
import { FaRegMap } from 'react-icons/fa';
|
||||||
|
import { sendTelemetryEvent } from '../../../telemetry';
|
||||||
|
|
||||||
type TopNavProps = {
|
type TopNavProps = {
|
||||||
location: RouteComponentProps<unknown, unknown>['location'];
|
location: RouteComponentProps<unknown, unknown>['location'];
|
||||||
@ -25,6 +28,12 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
|
|||||||
dataTestVal: 'rest-explorer-link',
|
dataTestVal: 'rest-explorer-link',
|
||||||
title: 'REST',
|
title: 'REST',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'schema-registry',
|
||||||
|
link: '/api/schema-registry',
|
||||||
|
dataTestVal: 'schema-registry-link',
|
||||||
|
title: 'Schema Registry',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -68,13 +77,30 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
|
|||||||
: 'border-white hover:border-gray-100'
|
: 'border-white hover:border-gray-100'
|
||||||
}`}
|
}`}
|
||||||
key={section.key}
|
key={section.key}
|
||||||
|
onClick={() => {
|
||||||
|
// Send Telemetry data for Schema Registry tab
|
||||||
|
if (section.key === 'schema-registry') {
|
||||||
|
sendTelemetryEvent({
|
||||||
|
type: 'CLICK_EVENT',
|
||||||
|
data: {
|
||||||
|
id: 'schema-registry-top-nav-tab',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
to={section.link}
|
to={section.link}
|
||||||
data-test={section.dataTestVal}
|
data-test={section.dataTestVal}
|
||||||
className="text-gray-600 font-semibold no-underline hover:text-gray-600 hover:no-underline focus:text-gray-600 focus:no-underline"
|
className="flex text-gray-600 font-semibold no-underline hover:text-gray-600 hover:no-underline focus:text-gray-600 focus:no-underline"
|
||||||
>
|
>
|
||||||
{section.title}
|
{section.title}
|
||||||
|
{section.key === 'schema-registry' && (
|
||||||
|
<IconTooltip
|
||||||
|
icon={<FaRegMap />}
|
||||||
|
message="Detect breaking and dangerous changes, view schema change history. Keep your GraphQL services safe and reliable! 🚀"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
|
|
||||||
import { useEELiteAccess } from '../../../features/EETrial';
|
import { useEELiteAccess } from '../../../features/EETrial';
|
||||||
import { getQueryResponseCachingRoute } from '../../../utils/routeUtils';
|
import { getQueryResponseCachingRoute } from '../../../utils/routeUtils';
|
||||||
import { isCloudConsole } from '../../../utils/cloudConsole';
|
|
||||||
import { isOpenTelemetrySupported } from '../../../utils/proConsole';
|
import { isOpenTelemetrySupported } from '../../../utils/proConsole';
|
||||||
|
|
||||||
export interface Metadata {
|
export interface Metadata {
|
||||||
@ -33,8 +32,7 @@ type SectionDataKey =
|
|||||||
| 'security'
|
| 'security'
|
||||||
| 'monitoring'
|
| 'monitoring'
|
||||||
| 'performance'
|
| 'performance'
|
||||||
| 'about'
|
| 'about';
|
||||||
| 'graphql';
|
|
||||||
|
|
||||||
const Sidebar: React.FC<SidebarProps> = ({ location, metadata }) => {
|
const Sidebar: React.FC<SidebarProps> = ({ location, metadata }) => {
|
||||||
const eeLiteAccess = useEELiteAccess(globals);
|
const eeLiteAccess = useEELiteAccess(globals);
|
||||||
@ -47,25 +45,6 @@ const Sidebar: React.FC<SidebarProps> = ({ location, metadata }) => {
|
|||||||
label: 'Metadata',
|
label: 'Metadata',
|
||||||
items: [],
|
items: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
|
||||||
isCloudConsole(globals) &&
|
|
||||||
(globals.userRole === 'admin' || globals.userRole === 'owner')
|
|
||||||
) {
|
|
||||||
sectionsData.graphql = {
|
|
||||||
key: 'graphql',
|
|
||||||
label: 'GraphQL',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
key: 'schema-registry',
|
|
||||||
label: 'Schema Registry (Beta)',
|
|
||||||
route: '/settings/schema-registry',
|
|
||||||
dataTestVal: 'metadata-schema-registry-link',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionsData.metadata.items.push({
|
sectionsData.metadata.items.push({
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
label: 'Metadata Actions',
|
label: 'Metadata Actions',
|
||||||
|
@ -6,23 +6,19 @@ import { SCHEMA_REGISTRY_FEATURE_NAME } from '../constants';
|
|||||||
import { FaBell } from 'react-icons/fa';
|
import { FaBell } from 'react-icons/fa';
|
||||||
import { IconTooltip } from '../../../new-components/Tooltip';
|
import { IconTooltip } from '../../../new-components/Tooltip';
|
||||||
import { AlertsDialog } from './AlertsDialog';
|
import { AlertsDialog } from './AlertsDialog';
|
||||||
import { Badge } from '../../../new-components/Badge';
|
import { Analytics, InitializeTelemetry } from '../../Analytics';
|
||||||
import { SCHEMA_REGISTRY_REF_URL } from '../constants';
|
|
||||||
import { Analytics } from '../../Analytics';
|
|
||||||
import { useGetV2Info } from '../hooks/useGetV2Info';
|
import { useGetV2Info } from '../hooks/useGetV2Info';
|
||||||
|
import { telemetryUserEventsTracker } from '../../../telemetry';
|
||||||
|
|
||||||
const SchemaRegistryHeader: React.VFC = () => {
|
const SchemaRegistryHeader: React.VFC = () => {
|
||||||
const [isAlertModalOpen, setIsAlertModalOpen] = useState(false);
|
const [isAlertModalOpen, setIsAlertModalOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full pl-12 mb-2">
|
||||||
<div className="flex mb-xs mt-md w-full">
|
<div className="flex mb-xs mt-md w-full">
|
||||||
<h1 className="inline-block text-xl font-semibold mr-2 text-slate-900">
|
<h1 className="inline-block text-xl font-semibold mr-2 text-slate-900">
|
||||||
GraphQL Schema Registry
|
GraphQL Schema Registry
|
||||||
</h1>
|
</h1>
|
||||||
<Badge className="mx-2" color="blue">
|
|
||||||
BETA
|
|
||||||
</Badge>
|
|
||||||
<Analytics name="data-schema-registry-alerts-btn">
|
<Analytics name="data-schema-registry-alerts-btn">
|
||||||
<div
|
<div
|
||||||
className="flex text-lg mt-2 mx-2 cursor-pointer"
|
className="flex text-lg mt-2 mx-2 cursor-pointer"
|
||||||
@ -36,14 +32,6 @@ const SchemaRegistryHeader: React.VFC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Analytics>
|
</Analytics>
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
className="text-muted w-auto mb-xs text-md"
|
|
||||||
href={SCHEMA_REGISTRY_REF_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
What is Schema Registry?
|
|
||||||
</a>
|
|
||||||
<span className="text-muted text-md mb-2 italic">
|
<span className="text-muted text-md mb-2 italic">
|
||||||
GraphQL Schema Registry changes will only be retained for 14 days.
|
GraphQL Schema Registry changes will only be retained for 14 days.
|
||||||
</span>
|
</span>
|
||||||
@ -96,8 +84,9 @@ export const SchemaRegistryContainer: React.VFC<
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 flex flex-col w-full">
|
<div className="flex flex-col w-[80%] pl-10 ml-10 justify-center">
|
||||||
<SchemaRegistryHeader />
|
<SchemaRegistryHeader />
|
||||||
|
<InitializeTelemetry tracker={telemetryUserEventsTracker} skip={false} />
|
||||||
<SchemaRegistryBody
|
<SchemaRegistryBody
|
||||||
hasFeatureAccess={hasFeatureAccess}
|
hasFeatureAccess={hasFeatureAccess}
|
||||||
schemaId={schemaId}
|
schemaId={schemaId}
|
||||||
|
@ -4,42 +4,18 @@ import { useGetSchema } from '../hooks/useGetSchema';
|
|||||||
import { Tabs } from '../../../new-components/Tabs';
|
import { Tabs } from '../../../new-components/Tabs';
|
||||||
import { IconTooltip } from '../../../new-components/Tooltip';
|
import { IconTooltip } from '../../../new-components/Tooltip';
|
||||||
import { SchemaRow } from './SchemaRow';
|
import { SchemaRow } from './SchemaRow';
|
||||||
import { ChangeSummary } from './ChangeSummary';
|
|
||||||
import {
|
import {
|
||||||
findIfSubStringExists,
|
findIfSubStringExists,
|
||||||
schemaTransformFn,
|
schemaTransformFn,
|
||||||
getPublishTime,
|
getPublishTime,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import { RoleBasedSchema, Schema } from '../types';
|
import { RoleBasedSchema, Schema } from '../types';
|
||||||
import {
|
import { FaSearch, FaShareAlt } from 'react-icons/fa';
|
||||||
FaHome,
|
|
||||||
FaAngleRight,
|
|
||||||
FaFileImport,
|
|
||||||
FaSearch,
|
|
||||||
FaShareAlt,
|
|
||||||
} from 'react-icons/fa';
|
|
||||||
import { Input } from '../../../new-components/Form';
|
import { Input } from '../../../new-components/Form';
|
||||||
import AceEditor from 'react-ace';
|
import AceEditor from 'react-ace';
|
||||||
import { SearchableSelect } from '../../../components/Common';
|
import { SearchableSelect } from '../../../components/Common';
|
||||||
import { Analytics } from '../../Analytics';
|
import { Analytics } from '../../Analytics';
|
||||||
|
|
||||||
export const Breadcrumbs = () => (
|
|
||||||
<div className="flex items-center space-x-xs mb-4">
|
|
||||||
<Link
|
|
||||||
to="/settings/schema-registry"
|
|
||||||
className="cursor-pointer flex items-center text-muted hover:text-gray-900"
|
|
||||||
>
|
|
||||||
<FaHome className="mr-1.5" />
|
|
||||||
<span className="text-sm">Schema</span>
|
|
||||||
</Link>
|
|
||||||
<FaAngleRight className="text-muted" />
|
|
||||||
<div className="cursor-pointer flex items-center text-yellow-500">
|
|
||||||
<FaFileImport className="mr-1.5" />
|
|
||||||
<span className="text-sm">Roles</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
interface SchemaChangeDetailsProps {
|
interface SchemaChangeDetailsProps {
|
||||||
schemaId: string;
|
schemaId: string;
|
||||||
}
|
}
|
||||||
@ -113,16 +89,17 @@ const SchemasDetails: React.VFC<{
|
|||||||
/>
|
/>
|
||||||
<div className="px-4 mb-2">
|
<div className="px-4 mb-2">
|
||||||
<div className="flex mt-4">
|
<div className="flex mt-4">
|
||||||
<div className="flex-col ">
|
<div className="flex items-center ">
|
||||||
<div className="flex items-center">
|
<p className="font-bold text-gray-500 py-2">Published: </p>
|
||||||
<p className="font-bold text-gray-500 py-2">Published</p>
|
|
||||||
|
<div className="flex items-center ml-2 font-semibold text-gray-600">
|
||||||
|
<span>{getPublishTime(schema.created_at)}</span>
|
||||||
<IconTooltip message="The time at which this GraphQL schema was generated" />
|
<IconTooltip message="The time at which this GraphQL schema was generated" />
|
||||||
</div>
|
</div>
|
||||||
<span>{getPublishTime(schema.created_at)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-around ml-auto w-1/2">
|
<div className="flex items-center justify-around ml-auto w-1/2">
|
||||||
<div className="flex-col">
|
<div className="flex-col">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center mt-4">
|
||||||
<p className="font-bold text-gray-500 py-2">Hash</p>
|
<p className="font-bold text-gray-500 py-2">Hash</p>
|
||||||
<IconTooltip message="Hash of the GraphQL Schema SDL. Hash for two identical schema is identical." />
|
<IconTooltip message="Hash of the GraphQL Schema SDL. Hash for two identical schema is identical." />
|
||||||
</div>
|
</div>
|
||||||
@ -263,65 +240,64 @@ export const ChangesView: React.VFC<{
|
|||||||
changesList.filter(c => c.criticality.level === 'NON_BREAKING');
|
changesList.filter(c => c.criticality.level === 'NON_BREAKING');
|
||||||
|
|
||||||
const showBreakingChanges =
|
const showBreakingChanges =
|
||||||
!selectedChangeLevel || selectedChangeLevel === 'breaking';
|
(!selectedChangeLevel || selectedChangeLevel === 'breaking') &&
|
||||||
|
!!breakingChanges?.length;
|
||||||
const showDangerousChanges =
|
const showDangerousChanges =
|
||||||
!selectedChangeLevel || selectedChangeLevel === 'dangerous';
|
(!selectedChangeLevel || selectedChangeLevel === 'dangerous') &&
|
||||||
|
!!dangerousChanges?.length;
|
||||||
const showSafeChanges =
|
const showSafeChanges =
|
||||||
!selectedChangeLevel || selectedChangeLevel === 'safe';
|
(!selectedChangeLevel || selectedChangeLevel === 'safe') &&
|
||||||
|
!!safeChanges?.length;
|
||||||
|
|
||||||
const onFilterChange = (op: Option) => {
|
const onFilterChange = (op: Option) => {
|
||||||
setSelectedChangeLevel(op.value);
|
setSelectedChangeLevel(op.value);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex-col">
|
<div className="flex-col">
|
||||||
<div className="flex-col">
|
<div className="flex w-full mt-4 mb-4 justify-between items-center">
|
||||||
<div className="font-semibold text-lg mb-8 mt-8 text-gray-500">
|
|
||||||
Change Summary
|
|
||||||
</div>
|
|
||||||
<div className="mr-8">
|
|
||||||
<ChangeSummary changes={changes} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full border-b border-gray-300 my-8" />
|
|
||||||
<div className="flex w-full mb-4 justify-between items-center">
|
|
||||||
<div className="flex font-semibold text-lg text-gray-500">Changes</div>
|
<div className="flex font-semibold text-lg text-gray-500">Changes</div>
|
||||||
<div className="flex block w-[50%]">
|
<div className="flex w-[50%]">
|
||||||
<div className="flex w-full">
|
<div className="flex w-full">
|
||||||
<div className="px-2 w-1/2">
|
<div className="px-2 w-1/2">
|
||||||
<SearchableSelect
|
<Analytics name="schema-registry-schema-change-details-filter">
|
||||||
options={[
|
<SearchableSelect
|
||||||
{
|
options={[
|
||||||
value: 'breaking',
|
{
|
||||||
label: 'Breaking',
|
value: 'breaking',
|
||||||
},
|
label: 'Breaking',
|
||||||
{
|
},
|
||||||
value: 'dangerous',
|
{
|
||||||
label: 'Dangerous',
|
value: 'dangerous',
|
||||||
},
|
label: 'Dangerous',
|
||||||
{
|
},
|
||||||
value: 'safe',
|
{
|
||||||
label: 'Safe',
|
value: 'safe',
|
||||||
},
|
label: 'Safe',
|
||||||
]}
|
},
|
||||||
onChange={op => {
|
]}
|
||||||
onFilterChange(op as Option);
|
onChange={op => {
|
||||||
}}
|
onFilterChange(op as Option);
|
||||||
filterOption="prefix"
|
}}
|
||||||
placeholder="Filter"
|
filterOption="prefix"
|
||||||
isClearable={true}
|
placeholder="Filter"
|
||||||
/>
|
isClearable={true}
|
||||||
</div>
|
|
||||||
<div className="pr-2 w-1/2">
|
|
||||||
<label>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search"
|
|
||||||
name="search"
|
|
||||||
icon={<FaSearch />}
|
|
||||||
iconPosition="start"
|
|
||||||
onChange={handleSearch}
|
|
||||||
/>
|
/>
|
||||||
</label>
|
</Analytics>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pr-2 w-1/2">
|
||||||
|
<Analytics name="schema-registry-schema-change-details-search">
|
||||||
|
<label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search"
|
||||||
|
name="search"
|
||||||
|
icon={<FaSearch />}
|
||||||
|
iconPosition="start"
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</Analytics>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -329,31 +305,27 @@ export const ChangesView: React.VFC<{
|
|||||||
|
|
||||||
{showBreakingChanges && (
|
{showBreakingChanges && (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-md text-gray-500 mt-4">
|
<div className="flex items-center font-semibold text-md text-gray-500 mt-4">
|
||||||
Breaking
|
Breaking
|
||||||
</div>
|
<IconTooltip message="Breaking changes due the schema change" />
|
||||||
<div className="text-sm text-gray-600 pb-2">
|
|
||||||
Content for What is breaking on schema registry
|
|
||||||
</div>
|
</div>
|
||||||
<ChangesTable changes={breakingChanges} level="breaking" />
|
<ChangesTable changes={breakingChanges} level="breaking" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showDangerousChanges && (
|
{showDangerousChanges && dangerousChanges && (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-md text-gray-500 mt-4">
|
<div className="flex items-center font-semibold text-md text-gray-500 mt-4">
|
||||||
Dangerous
|
Dangerous
|
||||||
</div>
|
<IconTooltip message="Dangerous changes due the schema change" />
|
||||||
<div className="text-sm text-gray-600 pb-2">
|
|
||||||
Content for What is dangerous on schema registry
|
|
||||||
</div>
|
</div>
|
||||||
<ChangesTable changes={dangerousChanges} level="dangerous" />
|
<ChangesTable changes={dangerousChanges} level="dangerous" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showSafeChanges && (
|
{showSafeChanges && safeChanges && (
|
||||||
<div>
|
<div>
|
||||||
<div className="font-semibold text-md text-gray-500 mt-4">Safe</div>
|
<div className="flex items-center font-semibold text-md text-gray-500 mt-4">
|
||||||
<div className="text-sm text-gray-600 pb-2">
|
Safe
|
||||||
Content for What is safe on schema registry
|
<IconTooltip message="Safe changes due the schema change" />
|
||||||
</div>
|
</div>
|
||||||
<ChangesTable changes={safeChanges} level="safe" />
|
<ChangesTable changes={safeChanges} level="safe" />
|
||||||
</div>
|
</div>
|
||||||
@ -385,7 +357,7 @@ const ChangesTable: React.FC<ChangesTableProps> = ({ changes, level }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{changes && changes.length ? (
|
{changes &&
|
||||||
changes.map((change, index) => (
|
changes.map((change, index) => (
|
||||||
<tr
|
<tr
|
||||||
className={`${textColor[level]} border border-neutral-200 p-2`}
|
className={`${textColor[level]} border border-neutral-200 p-2`}
|
||||||
@ -393,12 +365,7 @@ const ChangesTable: React.FC<ChangesTableProps> = ({ changes, level }) => {
|
|||||||
>
|
>
|
||||||
<td className="pl-2">{change.message}</td>
|
<td className="pl-2">{change.message}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))}
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td className="pl-2">{`No ${level} changes`}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,7 +19,7 @@ import AceEditor from 'react-ace';
|
|||||||
export const Breadcrumbs = () => (
|
export const Breadcrumbs = () => (
|
||||||
<div className="flex items-center space-x-xs mb-4">
|
<div className="flex items-center space-x-xs mb-4">
|
||||||
<Link
|
<Link
|
||||||
to="/settings/schema-registry"
|
to="/api/schema-registry"
|
||||||
className="cursor-pointer flex items-center text-muted hover:text-gray-900"
|
className="cursor-pointer flex items-center text-muted hover:text-gray-900"
|
||||||
>
|
>
|
||||||
<FaHome className="mr-1.5" />
|
<FaHome className="mr-1.5" />
|
||||||
|
@ -78,7 +78,7 @@ export const SchemaRegistryHome: React.FC<SchemaRegistryHomeProps> = props => {
|
|||||||
if (schemaRoleID === EMPTY_UUID_STRING) {
|
if (schemaRoleID === EMPTY_UUID_STRING) {
|
||||||
if (latestAdminRoleID) {
|
if (latestAdminRoleID) {
|
||||||
setSelectedRoleID(latestAdminRoleID);
|
setSelectedRoleID(latestAdminRoleID);
|
||||||
dispatch(_push(`/settings/schema-registry/${latestAdminRoleID}`));
|
dispatch(_push(`/api/schema-registry/${latestAdminRoleID}`));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSelectedRoleID(schemaRoleID);
|
setSelectedRoleID(schemaRoleID);
|
||||||
@ -88,7 +88,7 @@ export const SchemaRegistryHome: React.FC<SchemaRegistryHomeProps> = props => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (latestAdminRoleID) {
|
if (latestAdminRoleID) {
|
||||||
setSelectedRoleID(latestAdminRoleID);
|
setSelectedRoleID(latestAdminRoleID);
|
||||||
dispatch(_push(`/settings/schema-registry/${latestAdminRoleID}`));
|
dispatch(_push(`/api/schema-registry/${latestAdminRoleID}`));
|
||||||
}
|
}
|
||||||
setSearchParam(pageNo);
|
setSearchParam(pageNo);
|
||||||
}, [latestAdminRoleID, pageNo]);
|
}, [latestAdminRoleID, pageNo]);
|
||||||
@ -118,8 +118,8 @@ export const SchemaRegistryHome: React.FC<SchemaRegistryHomeProps> = props => {
|
|||||||
fetchSchemasResponse.response
|
fetchSchemasResponse.response
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full">
|
<div className="flex w-[80%] justify-center">
|
||||||
<div className="w-1/4">
|
<div className="w-[20%]">
|
||||||
<SchemaChangeList
|
<SchemaChangeList
|
||||||
schemas={schemaList}
|
schemas={schemaList}
|
||||||
urlSchemaCard={URLSchemaCard}
|
urlSchemaCard={URLSchemaCard}
|
||||||
@ -172,27 +172,33 @@ export const SchemaChangeList: React.VFC<{
|
|||||||
return (
|
return (
|
||||||
<div className="overflow-x-auto rounded-md border-neutral-200 bg-white border mr-sm">
|
<div className="overflow-x-auto rounded-md border-neutral-200 bg-white border mr-sm">
|
||||||
<div className="w-full flex bg-gray-100 px-4 py-2">
|
<div className="w-full flex bg-gray-100 px-4 py-2">
|
||||||
<div className="flex text-base w-[69%] justify-start">
|
<div className="flex text-base w-[50%] justify-start">
|
||||||
<span className="text-sm font-bold">PUBLISHED SCHEMA</span>
|
<span className="text-sm font-bold">PUBLISHED SCHEMA</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
{schemas.length ? (
|
{schemas.length ? (
|
||||||
<div className="mb-md">
|
<div className="mb-md">
|
||||||
{schemaList.map((schema, index) => (
|
<div
|
||||||
<SchemaCard
|
style={{ maxHeight: '80vh' }}
|
||||||
cardKey={index}
|
className={`flex flex-col w-full max-h-400px overflow-y-scroll`}
|
||||||
openSchemaCardIndex={openSchemaCardIndex}
|
>
|
||||||
selectedRoleID={selectedRoleID}
|
{schemaList.map((schema, index) => (
|
||||||
createdAt={schema.created_at}
|
<SchemaCard
|
||||||
roles={schema.roles}
|
cardKey={index}
|
||||||
entryHash={schema.entry_hash}
|
openSchemaCardIndex={openSchemaCardIndex}
|
||||||
tags={schema.tags}
|
selectedRoleID={selectedRoleID}
|
||||||
dispatch={dispatch}
|
createdAt={schema.created_at}
|
||||||
handleClick={() => setIsOpenSchemaCardIndex(index)}
|
roles={schema.roles}
|
||||||
/>
|
entryHash={schema.entry_hash}
|
||||||
))}
|
tags={schema.tags}
|
||||||
<div className="flex w-full justify-center items-center">
|
dispatch={dispatch}
|
||||||
|
handleClick={() => setIsOpenSchemaCardIndex(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full justify-center items-center mt-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary items-center max-w-full justify-center inline-flex text-sm font-sans font-semibold border rounded shadow-sm focus-visible:outline-none h-btn px-sm mx-1 disabled:border-gray-300 disabled:opacity-60 disabled:cursor-not-allowed"
|
className="btn btn-primary items-center max-w-full justify-center inline-flex text-sm font-sans font-semibold border rounded shadow-sm focus-visible:outline-none h-btn px-sm mx-1 disabled:border-gray-300 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
disabled={pageNumber === 0}
|
disabled={pageNumber === 0}
|
||||||
@ -281,7 +287,8 @@ const SchemaCard: React.VFC<{
|
|||||||
handleClick,
|
handleClick,
|
||||||
} = props;
|
} = props;
|
||||||
const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);
|
const [isTagModalOpen, setIsTagModalOpen] = React.useState(false);
|
||||||
const [isRolesMenuOpen, setIsRolesMenuOpen] = useState(false);
|
const [isCurrentCardOpen, setIsCurrentCardOpen] = useState(false);
|
||||||
|
|
||||||
const [tagsList, setTagsList] = React.useState<SchemaRegistryTag[]>(tags);
|
const [tagsList, setTagsList] = React.useState<SchemaRegistryTag[]>(tags);
|
||||||
const onRemoveTag = (id: string) => {
|
const onRemoveTag = (id: string) => {
|
||||||
const filteredTags = tagsList.filter(
|
const filteredTags = tagsList.filter(
|
||||||
@ -289,92 +296,128 @@ const SchemaCard: React.VFC<{
|
|||||||
);
|
);
|
||||||
setTagsList(filteredTags);
|
setTagsList(filteredTags);
|
||||||
};
|
};
|
||||||
|
const adminIndex = roles.findIndex(role => role.role === 'admin');
|
||||||
|
|
||||||
|
// If "admin" is not at the 0 index, move it there
|
||||||
|
if (adminIndex !== 0 && adminIndex !== -1) {
|
||||||
|
const adminRole = roles.splice(adminIndex, 1)[0];
|
||||||
|
roles.unshift(adminRole);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTagsList(tags);
|
setTagsList(tags);
|
||||||
}, [tags]);
|
}, [tags]);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setIsRolesMenuOpen(cardKey === openSchemaCardIndex);
|
setIsCurrentCardOpen(cardKey === openSchemaCardIndex);
|
||||||
}, [cardKey, openSchemaCardIndex]);
|
}, [cardKey, openSchemaCardIndex]);
|
||||||
const handleRoleClick = (roleBasedChange: Role) => {
|
const handleRoleClick = (roleBasedChange: Role) => {
|
||||||
dispatch(_push(`/settings/schema-registry/${roleBasedChange.id}`));
|
console.log('click');
|
||||||
|
dispatch(_push(`/api/schema-registry/${roleBasedChange.id}`));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultShowAllRoles = isCurrentCardOpen || roles.length <= 3;
|
||||||
|
const RolesList = defaultShowAllRoles ? roles : roles.slice(0, 3);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full flex flex-col px-4 bg-white rounded"
|
className={`w-full flex flex-col px-4 bg-white rounded mb-1`}
|
||||||
onClick={handleClick}
|
onClick={() => {
|
||||||
|
if (!roles.some(role => role.id === selectedRoleID)) {
|
||||||
|
dispatch(_push(`/api/schema-registry/${roles[0].id}`));
|
||||||
|
}
|
||||||
|
handleClick();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col mt-2 justify-between h-full">
|
<div
|
||||||
<div className="flex text-gray-600 text-sm justify-start">
|
className={`mt-2 ${
|
||||||
|
isCurrentCardOpen ? 'bg-gray-100' : ' bg-white'
|
||||||
|
} hover:bg-gray-100 `}
|
||||||
|
>
|
||||||
|
<div className="flex mt-1 mb-2 text-gray-600 text-sm items-center justify-start h-full px-2">
|
||||||
<span>{getPublishTime(createdAt)}</span>
|
<span>{getPublishTime(createdAt)}</span>
|
||||||
|
<span>
|
||||||
|
<Analytics name="schema-registry-add-tag-btn">
|
||||||
|
{isTagModalOpen && (
|
||||||
|
<AddSchemaRegistryTagDialog
|
||||||
|
tagsList={tagsList}
|
||||||
|
setTagsList={setTagsList}
|
||||||
|
entryHash={entryHash}
|
||||||
|
onClose={() => {
|
||||||
|
setIsTagModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex flex-nowrap items-center justify-start ">
|
||||||
|
{tagsList &&
|
||||||
|
tagsList.map(schemaRegistryTag => (
|
||||||
|
<div className="mx-1 ">
|
||||||
|
<SchemaTag
|
||||||
|
schemaRegistryTag={schemaRegistryTag}
|
||||||
|
onRemove={onRemoveTag}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="ml-1" onClick={() => setIsTagModalOpen(true)}>
|
||||||
|
<span>
|
||||||
|
<IconTooltip
|
||||||
|
message="Add a Tag"
|
||||||
|
icon={<FaPlusCircle />}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Analytics>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`flex font-semibold text-md justify-end hover:text-gray-600 ${
|
className={`flex flex-col justify-start w-full pt-auto ${
|
||||||
isRolesMenuOpen ? 'text-gray-600' : 'text-gray-400'
|
isCurrentCardOpen ? 'bg-gray-100' : ''
|
||||||
} cursor-pointer`}
|
} rounded `}
|
||||||
>
|
>
|
||||||
{roles.length} {roles.length === 1 ? 'Role' : 'Roles'}
|
<div className="text-sm text-gray-500 font-bold mb-1 px-2">
|
||||||
<span className="mr-1">{<FaChevronDown />}</span>
|
{roles.length} {roles.length === 1 ? 'ROLE' : 'ROLES'}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isRolesMenuOpen && (
|
|
||||||
<div className="w-full pt-auto">
|
|
||||||
<div className="text-sm text-gray-500 font-bold mb-3">
|
|
||||||
{roles.length === 1 ? 'ROLE' : 'ROLES'}
|
|
||||||
</div>
|
</div>
|
||||||
{roles.map((roleBasedChange, index) => (
|
<div>
|
||||||
<div
|
{RolesList.map((roleBasedChange, index) => (
|
||||||
className={`flex w-full p-2 ${
|
<div
|
||||||
roleBasedChange.id === selectedRoleID ? 'bg-gray-100' : ''
|
className={`flex w-full px-2 py-1 ${
|
||||||
} rounded hover:bg-gray-200`}
|
isCurrentCardOpen && roleBasedChange.id === selectedRoleID
|
||||||
onClick={() => {
|
? 'bg-gray-200'
|
||||||
handleRoleClick(roleBasedChange);
|
: ''
|
||||||
}}
|
} rounded hover:bg-gray-200`}
|
||||||
key={index}
|
onClick={() => {
|
||||||
>
|
handleRoleClick(roleBasedChange);
|
||||||
<div className="flex items-center justify-between w-full rounded">
|
}}
|
||||||
<div className="text-base rounded cursor-pointer">
|
key={index}
|
||||||
<p className="text-sm text-teal-800 font-bold bg-gray-200 px-1 rounded">
|
>
|
||||||
{CapitalizeFirstLetter(roleBasedChange.role)}
|
<div className="flex items-center justify-between w-full rounded">
|
||||||
</p>
|
<div className="text-base rounded cursor-pointer">
|
||||||
|
<p className="text-sm text-teal-800 font-bold bg-gray-200 px-1 rounded">
|
||||||
|
{CapitalizeFirstLetter(roleBasedChange.role)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<FaChevronRight />
|
||||||
</div>
|
</div>
|
||||||
<FaChevronRight />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
{!defaultShowAllRoles && (
|
||||||
</div>
|
<Analytics name="schema-registry-see-more-roles-btn">
|
||||||
)}
|
<div
|
||||||
{isTagModalOpen && (
|
className="flex justify-center items-center cursor-pointer text-base hover:bg-gray-200 pt-1"
|
||||||
<AddSchemaRegistryTagDialog
|
//here there is no need for onClick as clicking anywhere on a schemaCard sets the schemaCard as open and defaultShowAllRoles will be set to true
|
||||||
tagsList={tagsList}
|
>
|
||||||
setTagsList={setTagsList}
|
<span className="text-gray font-semibold">
|
||||||
entryHash={entryHash}
|
See More Roles
|
||||||
onClose={() => {
|
</span>
|
||||||
setIsTagModalOpen(false);
|
<FaChevronDown />
|
||||||
}}
|
</div>
|
||||||
/>
|
</Analytics>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-nowrap items-center justify-start mt-2">
|
|
||||||
{tagsList &&
|
|
||||||
tagsList.map(schemaRegistryTag => (
|
|
||||||
<div className="mr-2 ">
|
|
||||||
<SchemaTag
|
|
||||||
schemaRegistryTag={schemaRegistryTag}
|
|
||||||
onRemove={onRemoveTag}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Analytics name="schema-registry-add-tag-btn">
|
|
||||||
<div
|
|
||||||
className="mt-[7px] ml-[-6px] pb-2"
|
|
||||||
onClick={() => setIsTagModalOpen(true)}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<IconTooltip message="Add a Tag" icon={<FaPlusCircle />} />
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Analytics>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full border-b border-gray-300 my-2"></div>
|
<div className="flex w-full border-b border-gray-300 my-1"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { Link, RouteComponentProps } from 'react-router';
|
import { Link, RouteComponentProps } from 'react-router';
|
||||||
import { FaCheckCircle, FaTimesCircle } from 'react-icons/fa';
|
import { FaCheckCircle, FaTimesCircle } from 'react-icons/fa';
|
||||||
import { LocationDescriptor } from 'history';
|
import { LocationDescriptor } from 'history';
|
||||||
|
import { sendTelemetryEvent } from '../../telemetry';
|
||||||
|
|
||||||
export type NavigationSidebarItem = {
|
export type NavigationSidebarItem = {
|
||||||
key: string;
|
key: string;
|
||||||
@ -50,6 +51,17 @@ export const NavigationSidebar = ({
|
|||||||
? 'text-amber-500'
|
? 'text-amber-500'
|
||||||
: 'text-muted'
|
: 'text-muted'
|
||||||
)}
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
// Check if section.key is "schema-registry" and trigger telemetry event
|
||||||
|
if (item.key === 'schema-registry') {
|
||||||
|
sendTelemetryEvent({
|
||||||
|
type: 'CLICK_EVENT',
|
||||||
|
data: {
|
||||||
|
id: 'schema-registry-settings-btn',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
|
@ -346,6 +346,11 @@ const routes = store => {
|
|||||||
<Route path="details/:name" component={RestEndpointDetailsPage} />
|
<Route path="details/:name" component={RestEndpointDetailsPage} />
|
||||||
<Route path="edit/:name" component={CreateRestView} />
|
<Route path="edit/:name" component={CreateRestView} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="schema-registry" component={SchemaRegistryContainer} />
|
||||||
|
<Route
|
||||||
|
path="schema-registry/:id"
|
||||||
|
component={SchemaRegistryContainer}
|
||||||
|
/>
|
||||||
<Route path="allow-list">
|
<Route path="allow-list">
|
||||||
<IndexRedirect to="detail" />
|
<IndexRedirect to="detail" />
|
||||||
<Route
|
<Route
|
||||||
|
Loading…
Reference in New Issue
Block a user