mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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
This commit is contained in:
parent
fcc1b5489d
commit
04e21a34af
@ -4,8 +4,19 @@ 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 { IconTooltip } from '../../../new-components/Tooltip';
|
||||||
import { FaRegMap } from 'react-icons/fa';
|
import { FaExclamationCircle, FaRegMap } from 'react-icons/fa';
|
||||||
import { sendTelemetryEvent } from '../../../telemetry';
|
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 = {
|
type TopNavProps = {
|
||||||
location: RouteComponentProps<unknown, unknown>['location'];
|
location: RouteComponentProps<unknown, unknown>['location'];
|
||||||
@ -60,7 +71,53 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
|
|||||||
}
|
}
|
||||||
return location.pathname.includes(link);
|
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 (
|
return (
|
||||||
<div className="flex justify-between items-center border-b border-gray-300 bg-white px-sm">
|
<div className="flex justify-between items-center border-b border-gray-300 bg-white px-sm">
|
||||||
<div className="flex px-1 w-full">
|
<div className="flex px-1 w-full">
|
||||||
@ -78,6 +135,9 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
|
|||||||
}`}
|
}`}
|
||||||
key={section.key}
|
key={section.key}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (showNotifications) {
|
||||||
|
setLSItem(LS_KEYS.lastViewedSchemaChange, change_recorded_at);
|
||||||
|
}
|
||||||
// Send Telemetry data for Schema Registry tab
|
// Send Telemetry data for Schema Registry tab
|
||||||
if (section.key === 'schema-registry') {
|
if (section.key === 'schema-registry') {
|
||||||
sendTelemetryEvent({
|
sendTelemetryEvent({
|
||||||
@ -97,8 +157,14 @@ const TopNav: React.FC<TopNavProps> = ({ location }) => {
|
|||||||
{section.title}
|
{section.title}
|
||||||
{section.key === 'schema-registry' && (
|
{section.key === 'schema-registry' && (
|
||||||
<IconTooltip
|
<IconTooltip
|
||||||
icon={<FaRegMap />}
|
icon={
|
||||||
message="Detect breaking and dangerous changes, view schema change history. Keep your GraphQL services safe and reliable! 🚀"
|
color ? (
|
||||||
|
<FaExclamationCircle style={{ color }} />
|
||||||
|
) : (
|
||||||
|
<FaRegMap />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
message={tooltipMessage}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -380,26 +380,28 @@ const SchemaCard: React.VFC<{
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{RolesList.map((roleBasedChange, index) => (
|
{RolesList.map((roleBasedChange, index) => (
|
||||||
<div
|
<Analytics name="schema-registry-schema-change-card">
|
||||||
className={`flex w-full px-2 py-1 ${
|
<div
|
||||||
isCurrentCardOpen && roleBasedChange.id === selectedRoleID
|
className={`flex w-full px-2 py-1 ${
|
||||||
? 'bg-gray-200'
|
isCurrentCardOpen && roleBasedChange.id === selectedRoleID
|
||||||
: ''
|
? 'bg-gray-200'
|
||||||
} rounded hover:bg-gray-200`}
|
: ''
|
||||||
onClick={() => {
|
} rounded hover:bg-gray-200`}
|
||||||
handleRoleClick(roleBasedChange);
|
onClick={() => {
|
||||||
}}
|
handleRoleClick(roleBasedChange);
|
||||||
key={index}
|
}}
|
||||||
>
|
key={index}
|
||||||
<div className="flex items-center justify-between w-full rounded">
|
>
|
||||||
<div className="text-base rounded cursor-pointer">
|
<div className="flex items-center justify-between w-full rounded">
|
||||||
<p className="text-sm text-teal-800 font-bold bg-gray-200 px-1 rounded">
|
<div className="text-base rounded cursor-pointer">
|
||||||
{CapitalizeFirstLetter(roleBasedChange.role)}
|
<p className="text-sm text-teal-800 font-bold bg-gray-200 px-1 rounded">
|
||||||
</p>
|
{CapitalizeFirstLetter(roleBasedChange.role)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<FaChevronRight />
|
||||||
</div>
|
</div>
|
||||||
<FaChevronRight />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Analytics>
|
||||||
))}
|
))}
|
||||||
{!defaultShowAllRoles && (
|
{!defaultShowAllRoles && (
|
||||||
<Analytics name="schema-registry-see-more-roles-btn">
|
<Analytics name="schema-registry-see-more-roles-btn">
|
||||||
|
@ -17,6 +17,8 @@ export const FETCH_SCHEMA_REGISTRY_DUMPS_V2_QUERY_NAME =
|
|||||||
export const FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY_NAME =
|
export const FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY_NAME =
|
||||||
'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
|
// 5 minutes as default stale time
|
||||||
export const SCHEMA_REGISTRY_REFRESH_TIME = 5 * 60 * 1000;
|
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 EMPTY_UUID_STRING = '00000000-0000-0000-0000-000000000000';
|
||||||
|
|
||||||
export const DEFAULT_TAG_COLOR = '#000000';
|
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';
|
||||||
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
@ -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(`
|
export const FETCH_SCHEMA_REGISTRY_DUMPS_V1_QUERY = gql(`
|
||||||
query fetchSchemaRegistryDumpsV1($projectId: uuid!, $limit: Int!, $offset: Int!, $changeTimestamp: timestamptz!) {
|
query fetchSchemaRegistryDumpsV1($projectId: uuid!, $limit: Int!, $offset: Int!, $changeTimestamp: timestamptz!) {
|
||||||
|
@ -90,6 +90,11 @@ export type GetSchemaRegstiryDumpsV2AggregateResponseWithError = {
|
|||||||
errors?: GraphQLError[];
|
errors?: GraphQLError[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetSchemaRegistryNotificationResponseWithError = {
|
||||||
|
data?: GetSchemaRegistryNotificationResponse;
|
||||||
|
errors?: GraphQLError[];
|
||||||
|
};
|
||||||
|
|
||||||
export type GetSchemaRegstiryDumpsV2AggregateResponse = {
|
export type GetSchemaRegstiryDumpsV2AggregateResponse = {
|
||||||
schema_registry_dumps_v2_aggregate: SchemaRegistryDumpsAggregate;
|
schema_registry_dumps_v2_aggregate: SchemaRegistryDumpsAggregate;
|
||||||
schema_registry_dumps_v2: SchemaRegistryChangeRecordedAt[];
|
schema_registry_dumps_v2: SchemaRegistryChangeRecordedAt[];
|
||||||
@ -101,6 +106,12 @@ export type GetSchemaRegstiryDumpsV1AggregateResponseWithError = {
|
|||||||
export type GetSchemaRegstiryDumpsV1AggregateResponse = {
|
export type GetSchemaRegstiryDumpsV1AggregateResponse = {
|
||||||
schema_registry_dumps_aggregate: SchemaRegistryDumpsAggregate;
|
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 = {
|
export type SchemaRegistryChangeRecordedAt = {
|
||||||
change_recorded_at: string;
|
change_recorded_at: string;
|
||||||
};
|
};
|
||||||
|
@ -126,6 +126,7 @@ export const LS_KEYS = {
|
|||||||
notificationsLastSeen: 'notifications:lastSeen',
|
notificationsLastSeen: 'notifications:lastSeen',
|
||||||
authState: 'AUTH_STATE',
|
authState: 'AUTH_STATE',
|
||||||
skipOnboarding: 'SKIP_CLOUD_ONBOARDING',
|
skipOnboarding: 'SKIP_CLOUD_ONBOARDING',
|
||||||
|
lastViewedSchemaChange: 'LAST_VIEWED_SCHEMA_CHANGE',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearGraphiqlLS = () => {
|
export const clearGraphiqlLS = () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user