From 04e21a34af0a8953f1303ef8072e728fe1852d64 Mon Sep 17 00:00:00 2001 From: Aaysha <109507451+aayshasura@users.noreply.github.com> Date: Mon, 30 Oct 2023 03:40:44 +0530 Subject: [PATCH] console: Add notification to Schema Registry Tab based on latest schema change PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10402 GitOrigin-RevId: 2e1945bc34b42d0512def217168758ff7393b969 --- .../Services/ApiExplorer/TopNav.tsx | 72 ++++++++++++++++++- .../components/SchemaRegistryHome.tsx | 38 +++++----- .../lib/features/SchemaRegistry/constants.ts | 13 ++++ .../useGetSchemaRegistryNotificationColor.ts | 52 ++++++++++++++ .../lib/features/SchemaRegistry/queries.ts | 10 +++ .../src/lib/features/SchemaRegistry/types.ts | 11 +++ .../legacy-ce/src/lib/utils/localStorage.ts | 1 + 7 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/hooks/useGetSchemaRegistryNotificationColor.ts diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/TopNav.tsx b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/TopNav.tsx index fcbc276ded3..0cd7d8ee445 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/TopNav.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/TopNav.tsx @@ -4,8 +4,19 @@ import { isProConsole } from '../../../utils/proConsole'; import { useEELiteAccess } from '../../../features/EETrial'; import globals from '../../../Globals'; import { IconTooltip } from '../../../new-components/Tooltip'; -import { FaRegMap } from 'react-icons/fa'; +import { FaExclamationCircle, FaRegMap } from 'react-icons/fa'; import { sendTelemetryEvent } from '../../../telemetry'; +import { useGetSchemaRegistryNotificationColor } from '../../../features/SchemaRegistry/hooks/useGetSchemaRegistryNotificationColor'; +import { getLSItem, LS_KEYS, setLSItem } from '../../../utils'; +import { + BreakingChangesColor, + BreakingChangesTooltipMessage, + DangerousChangesColor, + DangerousChangesTooltipMessage, + DefaultToopTipMessage, + SafeChangesColor, + SafeChangesTooltipMessage, +} from '../../../features/SchemaRegistry/constants'; type TopNavProps = { location: RouteComponentProps['location']; @@ -60,7 +71,53 @@ const TopNav: React.FC = ({ location }) => { } return location.pathname.includes(link); }; + const projectID = globals.hasuraCloudProjectId || ''; + const fetchSchemaRegistryNotificationData = + useGetSchemaRegistryNotificationColor(projectID); + let color = ''; + let tooltipMessage = DefaultToopTipMessage; + let change_recorded_at = ''; + let showNotifications = false; + if (fetchSchemaRegistryNotificationData.kind === 'success') { + const data = + fetchSchemaRegistryNotificationData?.response + ?.schema_registry_dumps_v2[0] || []; + if ( + data && + data.diff_with_previous_schema[0] && + data.diff_with_previous_schema[0].schema_diff_data && + data.change_recorded_at + ) { + const changes = data.diff_with_previous_schema[0].schema_diff_data; + // Check if there's a change with a criticality level of "BREAKING" + const hasBreakingChange = changes.some( + change => change.criticality && change.criticality.level === 'BREAKING' + ); + const hasDangerousChange = changes.some( + change => change.criticality && change.criticality.level === 'DANGEROUS' + ); + const last_viewed_change = getLSItem(LS_KEYS.lastViewedSchemaChange); + if ( + (!last_viewed_change || last_viewed_change < data.change_recorded_at) && + changes + ) { + if (hasBreakingChange) { + color = BreakingChangesColor; + tooltipMessage = BreakingChangesTooltipMessage; + } else if (hasDangerousChange) { + //gold color instead of yellow to be more visible + color = DangerousChangesColor; + tooltipMessage = DangerousChangesTooltipMessage; + } else { + color = SafeChangesColor; + tooltipMessage = SafeChangesTooltipMessage; + } + change_recorded_at = data.change_recorded_at; + showNotifications = true; + } + } + } return (
@@ -78,6 +135,9 @@ const TopNav: React.FC = ({ location }) => { }`} key={section.key} onClick={() => { + if (showNotifications) { + setLSItem(LS_KEYS.lastViewedSchemaChange, change_recorded_at); + } // Send Telemetry data for Schema Registry tab if (section.key === 'schema-registry') { sendTelemetryEvent({ @@ -97,8 +157,14 @@ const TopNav: React.FC = ({ location }) => { {section.title} {section.key === 'schema-registry' && ( } - message="Detect breaking and dangerous changes, view schema change history. Keep your GraphQL services safe and reliable! 🚀" + icon={ + color ? ( + + ) : ( + + ) + } + message={tooltipMessage} /> )} diff --git a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/components/SchemaRegistryHome.tsx b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/components/SchemaRegistryHome.tsx index 89de7edeacd..d3e1823118d 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/components/SchemaRegistryHome.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/components/SchemaRegistryHome.tsx @@ -380,26 +380,28 @@ const SchemaCard: React.VFC<{
{RolesList.map((roleBasedChange, index) => ( -
{ - handleRoleClick(roleBasedChange); - }} - key={index} - > -
-
-

- {CapitalizeFirstLetter(roleBasedChange.role)} -

+ +
{ + handleRoleClick(roleBasedChange); + }} + key={index} + > +
+
+

+ {CapitalizeFirstLetter(roleBasedChange.role)} +

+
+
-
-
+ ))} {!defaultShowAllRoles && ( diff --git a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/constants.ts b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/constants.ts index 274c75ac376..ea322914e01 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/constants.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/constants.ts @@ -17,6 +17,8 @@ export const FETCH_SCHEMA_REGISTRY_DUMPS_V2_QUERY_NAME = export const FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY_NAME = 'FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY_NAME'; +export const FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY_NAME = + 'FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY_NAME'; // 5 minutes as default stale time export const SCHEMA_REGISTRY_REFRESH_TIME = 5 * 60 * 1000; @@ -29,3 +31,14 @@ export const SCHEMA_LIST_FETCH_BATCH_SIZE = 10; export const EMPTY_UUID_STRING = '00000000-0000-0000-0000-000000000000'; export const DEFAULT_TAG_COLOR = '#000000'; + +export const BreakingChangesTooltipMessage = 'Breaking changes detected!'; +export const DangerousChangesTooltipMessage = 'Dangerous changes detected!'; +export const SafeChangesTooltipMessage = 'Checkout the Latest Schema Change'; + +export const DefaultToopTipMessage = + 'Detect breaking and dangerous changes, view schema change history. Keep your GraphQL services safe and reliable! 🚀'; + +export const BreakingChangesColor = 'red'; +export const DangerousChangesColor = '#FFD700'; +export const SafeChangesColor = 'green'; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/hooks/useGetSchemaRegistryNotificationColor.ts b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/hooks/useGetSchemaRegistryNotificationColor.ts new file mode 100644 index 00000000000..24b32ed84de --- /dev/null +++ b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/hooks/useGetSchemaRegistryNotificationColor.ts @@ -0,0 +1,52 @@ +import { useQuery } from 'react-query'; +import { FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY_NAME } from '../constants'; +import { FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY } from '../queries'; +import { GetSchemaRegistryNotificationResponseWithError } from '../types'; +import { schemaRegsitryControlPlaneClient } from '../utils'; + +type FetchSchemaRegistryNotificationResponse = + | { + kind: 'loading'; + } + | { + kind: 'error'; + } + | { + kind: 'success'; + response: NonNullable< + GetSchemaRegistryNotificationResponseWithError['data'] + >; + }; + +export const useGetSchemaRegistryNotificationColor = ( + projectId: string +): FetchSchemaRegistryNotificationResponse => { + const fetchSchemaRegistyNotificationFn = (projectId: string) => { + return schemaRegsitryControlPlaneClient.query< + GetSchemaRegistryNotificationResponseWithError, + { projectId: string } + >(FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY, { + projectId: projectId, + }); + }; + const { data, error, isLoading } = useQuery({ + queryKey: FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY_NAME, + queryFn: () => fetchSchemaRegistyNotificationFn(projectId), + refetchOnMount: 'always', + refetchOnWindowFocus: true, + }); + if (isLoading) { + return { + kind: 'loading', + }; + } + if (error || !data || !!data?.errors || !data?.data) { + return { + kind: 'error', + }; + } + return { + kind: 'success', + response: data.data, + }; +}; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/queries.ts b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/queries.ts index 5170c58ab98..d476efeb0a9 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/queries.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/queries.ts @@ -134,6 +134,16 @@ query fetchSchemaRegistryDumpsV2($projectId: uuid!, $limit: Int!, $offset: Int!) } } `); +export const FETCH_SCHEMA_REGISTRY_NOTIFICATION_QUERY = gql(` +query fetchSchemaRegistryDumpsV2($projectId: uuid!) { + schema_registry_dumps_v2(where: {_and: [{project_id: {_eq: $projectId}, hasura_schema_role: {_eq: "admin"}}]}, order_by: {change_recorded_at: desc}, limit: 1) { + change_recorded_at + diff_with_previous_schema { + schema_diff_data + } + } +} +`); export const FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY = gql(` query fetchSchemaRegistryDumpsV1($projectId: uuid!, $limit: Int!, $offset: Int!, $changeTimestamp: timestamptz!) { diff --git a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/types.ts b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/types.ts index 73b825ce398..f9b276813df 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/types.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/SchemaRegistry/types.ts @@ -90,6 +90,11 @@ export type GetSchemaRegstiryDumpsV2AggregateResponseWithError = { errors?: GraphQLError[]; }; +export type GetSchemaRegistryNotificationResponseWithError = { + data?: GetSchemaRegistryNotificationResponse; + errors?: GraphQLError[]; +}; + export type GetSchemaRegstiryDumpsV2AggregateResponse = { schema_registry_dumps_v2_aggregate: SchemaRegistryDumpsAggregate; schema_registry_dumps_v2: SchemaRegistryChangeRecordedAt[]; @@ -101,6 +106,12 @@ export type GetSchemaRegstiryDumpsV1AggregateResponseWithError = { export type GetSchemaRegstiryDumpsV1AggregateResponse = { schema_registry_dumps_aggregate: SchemaRegistryDumpsAggregate; }; +export type GetSchemaRegistryNotificationResponse = { + schema_registry_dumps_v2: SchemaRegsitryNotificationData[]; +}; +export type SchemaRegsitryNotificationData = SchemaRegistryChangeRecordedAt & { + diff_with_previous_schema: SchemaDiffData[]; +}; export type SchemaRegistryChangeRecordedAt = { change_recorded_at: string; }; diff --git a/frontend/libs/console/legacy-ce/src/lib/utils/localStorage.ts b/frontend/libs/console/legacy-ce/src/lib/utils/localStorage.ts index 2d572d16bec..6521ae3077e 100644 --- a/frontend/libs/console/legacy-ce/src/lib/utils/localStorage.ts +++ b/frontend/libs/console/legacy-ce/src/lib/utils/localStorage.ts @@ -126,6 +126,7 @@ export const LS_KEYS = { notificationsLastSeen: 'notifications:lastSeen', authState: 'AUTH_STATE', skipOnboarding: 'SKIP_CLOUD_ONBOARDING', + lastViewedSchemaChange: 'LAST_VIEWED_SCHEMA_CHANGE', }; export const clearGraphiqlLS = () => {