diff --git a/frontend/webapp/app/(overview)/overview/page.tsx b/frontend/webapp/app/(overview)/overview/page.tsx index c24075f34..5d971be73 100644 --- a/frontend/webapp/app/(overview)/overview/page.tsx +++ b/frontend/webapp/app/(overview)/overview/page.tsx @@ -1,6 +1,7 @@ 'use client'; import React from 'react'; import dynamic from 'next/dynamic'; +import { useSSE } from '@/hooks'; const ToastList = dynamic(() => import('@/components/notification/toast-list'), { ssr: false }); const AllDrawers = dynamic(() => import('@/components/overview/all-drawers'), { ssr: false }); @@ -8,6 +9,8 @@ const AllModals = dynamic(() => import('@/components/overview/all-modals'), { ss const OverviewDataFlowContainer = dynamic(() => import('@/containers/main/overview/overview-data-flow'), { ssr: false }); export default function MainPage() { + useSSE(); + return ( <> diff --git a/frontend/webapp/app/layout.tsx b/frontend/webapp/app/layout.tsx index f0254a8b5..a662b286e 100644 --- a/frontend/webapp/app/layout.tsx +++ b/frontend/webapp/app/layout.tsx @@ -1,7 +1,6 @@ 'use client'; import './globals.css'; import React from 'react'; -import { useSSE } from '@/hooks'; import { METADATA } from '@/utils'; import { ApolloWrapper } from '@/lib'; import { ThemeProviderWrapper } from '@/styles'; @@ -15,8 +14,6 @@ const LAYOUT_STYLE: React.CSSProperties = { }; export default function RootLayout({ children }: { children: React.ReactNode }) { - useSSE(); - return ( diff --git a/frontend/webapp/app/page.tsx b/frontend/webapp/app/page.tsx index de97cf7be..9df649d3c 100644 --- a/frontend/webapp/app/page.tsx +++ b/frontend/webapp/app/page.tsx @@ -1,20 +1,22 @@ 'use client'; import { useEffect } from 'react'; +import { useConfig } from '@/hooks'; +import { CenterThis } from '@/styles'; +import { ROUTES, CONFIG } from '@/utils'; +import { NOTIFICATION_TYPE } from '@/types'; import { useRouter } from 'next/navigation'; -import { useNotify, useConfig } from '@/hooks'; +import { useNotificationStore } from '@/store'; import { FadeLoader } from '@/reuseable-components'; -import { ROUTES, CONFIG, NOTIFICATION } from '@/utils'; -import { CenterThis } from '@/styles'; export default function App() { const router = useRouter(); - const notify = useNotify(); const { data, error } = useConfig(); + const { addNotification } = useNotificationStore(); useEffect(() => { if (error) { - notify({ - type: NOTIFICATION.ERROR, + addNotification({ + type: NOTIFICATION_TYPE.ERROR, title: error.name, message: error.message, }); diff --git a/frontend/webapp/components/modals/cancel-warning/index.tsx b/frontend/webapp/components/modals/cancel-warning/index.tsx index 82221bc9f..5e4b760d1 100644 --- a/frontend/webapp/components/modals/cancel-warning/index.tsx +++ b/frontend/webapp/components/modals/cancel-warning/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { NOTIFICATION_TYPE } from '@/types'; import { WarningModal } from '@/reuseable-components'; interface Props { @@ -18,7 +19,7 @@ const CancelWarning: React.FC = ({ isOpen, noOverlay, name, onApprove, on description='Are you sure you want to cancel?' approveButton={{ text: 'Confirm', - variant: 'warning', + variant: NOTIFICATION_TYPE.WARNING, onClick: onApprove, }} denyButton={{ diff --git a/frontend/webapp/components/modals/delete-warning/index.tsx b/frontend/webapp/components/modals/delete-warning/index.tsx index 01b7978c3..e59fa881a 100644 --- a/frontend/webapp/components/modals/delete-warning/index.tsx +++ b/frontend/webapp/components/modals/delete-warning/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { OVERVIEW_ENTITY_TYPES } from '@/types'; import { WarningModal } from '@/reuseable-components'; +import { NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES } from '@/types'; interface Props { isOpen: boolean; @@ -24,7 +24,7 @@ const DeleteWarning: React.FC = ({ isOpen, noOverlay, name, type, isLastI note={ isLastItem ? { - type: 'warning', + type: NOTIFICATION_TYPE.WARNING, title: `You're about to ${actionText} the last ${type}`, message: 'This will break your pipeline!', } diff --git a/frontend/webapp/components/notification/notification-manager.tsx b/frontend/webapp/components/notification/notification-manager.tsx index 36586c700..c0c752bfe 100644 --- a/frontend/webapp/components/notification/notification-manager.tsx +++ b/frontend/webapp/components/notification/notification-manager.tsx @@ -6,7 +6,7 @@ import { useNotificationStore } from '@/store'; import { ACTION, getStatusIcon } from '@/utils'; import { useOnClickOutside, useTimeAgo } from '@/hooks'; import theme, { hexPercentValues } from '@/styles/theme'; -import type { Notification, NotificationType } from '@/types'; +import { NOTIFICATION_TYPE, type Notification } from '@/types'; import { IconButton, NoDataFound, Text } from '@/reuseable-components'; const RelativeContainer = styled.div` @@ -82,14 +82,14 @@ export const NotificationManager = () => { return ( - + logo {isOpen && ( - + - Notifications{' '} + Notifications {!!unseenCount && ( {unseenCount} new @@ -126,7 +126,7 @@ const NotifCard = styled.div` } `; -const StatusIcon = styled.div<{ $type: NotificationType }>` +const StatusIcon = styled.div<{ $type: NOTIFICATION_TYPE }>` background-color: ${({ $type, theme }) => theme.text[$type] + hexPercentValues['012']}; border-radius: 8px; width: 36px; @@ -175,7 +175,7 @@ const NotificationListItem: React.FC void }> = ( } }} > - + status diff --git a/frontend/webapp/components/overview/add-entity/index.tsx b/frontend/webapp/components/overview/add-entity/index.tsx index b9c449995..5323720ee 100644 --- a/frontend/webapp/components/overview/add-entity/index.tsx +++ b/frontend/webapp/components/overview/add-entity/index.tsx @@ -1,11 +1,11 @@ +import React, { useState, useRef } from 'react'; import Image from 'next/image'; import theme from '@/styles/theme'; -import { useOnClickOutside } from '@/hooks'; -import React, { useState, useRef } from 'react'; +import { useModalStore } from '@/store'; import styled, { css } from 'styled-components'; -import { useBooleanStore, useModalStore } from '@/store'; -import { DropdownOption, OVERVIEW_ENTITY_TYPES } from '@/types'; +import { useComputePlatform, useOnClickOutside } from '@/hooks'; import { Button, FadeLoader, Text } from '@/reuseable-components'; +import { type DropdownOption, OVERVIEW_ENTITY_TYPES } from '@/types'; // Styled components for the dropdown UI const Container = styled.div` @@ -65,13 +65,13 @@ const DEFAULT_OPTIONS: DropdownOption[] = [ { id: OVERVIEW_ENTITY_TYPES.DESTINATION, value: 'Destination' }, ]; -interface AddEntityButtonDropdownProps { +interface Props { options?: DropdownOption[]; placeholder?: string; } -const AddEntity: React.FC = ({ options = DEFAULT_OPTIONS, placeholder = 'ADD...' }) => { - const { isPolling } = useBooleanStore(); +const AddEntity: React.FC = ({ options = DEFAULT_OPTIONS, placeholder = 'ADD...' }) => { + const { loading } = useComputePlatform(); const { currentModal, setCurrentModal } = useModalStore(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); @@ -91,7 +91,7 @@ const AddEntity: React.FC = ({ options = DEFAULT_O return ( - {isPolling ? : Add} + {loading ? : Add} {placeholder} diff --git a/frontend/webapp/containers/main/destinations/add-destination/index.tsx b/frontend/webapp/containers/main/destinations/add-destination/index.tsx index b4bc07f54..0e293f753 100644 --- a/frontend/webapp/containers/main/destinations/add-destination/index.tsx +++ b/frontend/webapp/containers/main/destinations/add-destination/index.tsx @@ -2,15 +2,16 @@ import React, { useState } from 'react'; import Image from 'next/image'; import { ROUTES } from '@/utils'; import theme from '@/styles/theme'; +import { CenterThis } from '@/styles'; import { useAppStore } from '@/store'; import styled from 'styled-components'; import { SetupHeader } from '@/components'; import { useRouter } from 'next/navigation'; -import { useDestinationCRUD, useSourceCRUD } from '@/hooks'; +import { NOTIFICATION_TYPE } from '@/types'; import { DestinationModal } from '../destination-modal'; +import { useDestinationCRUD, useSourceCRUD } from '@/hooks'; import { ConfiguredDestinationsList } from './configured-destinations-list'; import { Button, FadeLoader, NotificationNote, SectionTitle, Text } from '@/reuseable-components'; -import { CenterThis } from '@/styles'; const ContentWrapper = styled.div` width: 640px; @@ -93,7 +94,7 @@ export function AddDestinationContainer() { {!isLoading && isSourcesListEmpty() && ( (); + const [connectionStatus, setConnectionStatus] = useState(); const dirtyForm = () => { setIsFormDirty(true); @@ -87,11 +87,11 @@ export function DestinationFormBody({ isUpdate, destination, formData, formError status={connectionStatus} onError={() => { setIsFormDirty(false); - setConnectionStatus('error'); + setConnectionStatus(NOTIFICATION_TYPE.ERROR); }} onSuccess={() => { setIsFormDirty(false); - setConnectionStatus('success'); + setConnectionStatus(NOTIFICATION_TYPE.SUCCESS); }} validateForm={validateForm} /> @@ -101,9 +101,9 @@ export function DestinationFormBody({ isUpdate, destination, formData, formError {testConnectionSupported && ( - {connectionStatus === 'error' && } - {connectionStatus === 'success' && } - {!connectionStatus && } + {connectionStatus === NOTIFICATION_TYPE.ERROR && } + {connectionStatus === NOTIFICATION_TYPE.SUCCESS && } + {!connectionStatus && } )} diff --git a/frontend/webapp/containers/main/destinations/destination-form-body/test-connection/index.tsx b/frontend/webapp/containers/main/destinations/destination-form-body/test-connection/index.tsx index 15af5b783..a08ad2ac4 100644 --- a/frontend/webapp/containers/main/destinations/destination-form-body/test-connection/index.tsx +++ b/frontend/webapp/containers/main/destinations/destination-form-body/test-connection/index.tsx @@ -3,22 +3,22 @@ import Image from 'next/image'; import theme from '@/styles/theme'; import { getStatusIcon } from '@/utils'; import { useTestConnection } from '@/hooks'; -import type { DestinationInput } from '@/types'; import styled, { css } from 'styled-components'; import { Button, FadeLoader, Text } from '@/reuseable-components'; +import { type DestinationInput, NOTIFICATION_TYPE } from '@/types'; -type Status = 'success' | 'error'; +export type ConnectionStatus = NOTIFICATION_TYPE.SUCCESS | NOTIFICATION_TYPE.ERROR; interface Props { destination: DestinationInput; disabled: boolean; - status?: Status; + status?: ConnectionStatus; onError: () => void; onSuccess: () => void; validateForm: () => boolean; } -const ActionButton = styled(Button)<{ $status?: Status }>` +const ActionButton = styled(Button)<{ $status?: ConnectionStatus }>` display: flex; align-items: center; gap: 8px; diff --git a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx index 21ca7e8c8..04b6e5f4e 100644 --- a/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx +++ b/frontend/webapp/containers/main/instrumentation-rules/rule-drawer/index.tsx @@ -2,14 +2,14 @@ import React, { useMemo, useState } from 'react'; import buildCard from './build-card'; import { RuleFormBody } from '../'; import styled from 'styled-components'; -import { useDrawerStore } from '@/store'; import { DataCard } from '@/reuseable-components'; import buildDrawerItem from './build-drawer-item'; import { RULE_OPTIONS } from '../rule-modal/rule-options'; import OverviewDrawer from '../../overview/overview-drawer'; -import { ACTION, DATA_CARDS, FORM_ALERTS, getRuleIcon, NOTIFICATION } from '@/utils'; -import { useInstrumentationRuleCRUD, useInstrumentationRuleFormData, useNotify } from '@/hooks'; -import { InstrumentationRuleType, OVERVIEW_ENTITY_TYPES, type InstrumentationRuleSpec } from '@/types'; +import { useDrawerStore, useNotificationStore } from '@/store'; +import { ACTION, DATA_CARDS, FORM_ALERTS, getRuleIcon } from '@/utils'; +import { useInstrumentationRuleCRUD, useInstrumentationRuleFormData } from '@/hooks'; +import { InstrumentationRuleType, NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES, type InstrumentationRuleSpec } from '@/types'; interface Props {} @@ -22,7 +22,7 @@ const FormContainer = styled.div` `; export const RuleDrawer: React.FC = () => { - const notify = useNotify(); + const { addNotification } = useNotificationStore(); const { selectedItem, setSelectedItem } = useDrawerStore(); const { formData, formErrors, handleFormChange, resetFormData, validateForm, loadFormWithDrawerItem } = useInstrumentationRuleFormData(); @@ -74,7 +74,7 @@ export const RuleDrawer: React.FC = () => { const handleEdit = (bool?: boolean) => { if (item.type === InstrumentationRuleType.UNKNOWN_TYPE) { - notify({ type: NOTIFICATION.WARNING, title: FORM_ALERTS.FORBIDDEN, message: FORM_ALERTS.CANNOT_EDIT_RULE, crdType: OVERVIEW_ENTITY_TYPES.RULE, target: id }); + addNotification({ type: NOTIFICATION_TYPE.WARNING, title: FORM_ALERTS.FORBIDDEN, message: FORM_ALERTS.CANNOT_EDIT_RULE, crdType: OVERVIEW_ENTITY_TYPES.RULE, target: id }); } else { setIsEditing(typeof bool === 'boolean' ? bool : true); } diff --git a/frontend/webapp/containers/main/instrumentation-rules/rule-modal/index.tsx b/frontend/webapp/containers/main/instrumentation-rules/rule-modal/index.tsx index f1b6f52cf..554fda070 100644 --- a/frontend/webapp/containers/main/instrumentation-rules/rule-modal/index.tsx +++ b/frontend/webapp/containers/main/instrumentation-rules/rule-modal/index.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { ACTION } from '@/utils'; import { RuleFormBody } from '../'; +import { NOTIFICATION_TYPE } from '@/types'; import { CenterThis, ModalBody } from '@/styles'; import { RULE_OPTIONS, RuleOption } from './rule-options'; import { useInstrumentationRuleCRUD, useInstrumentationRuleFormData, useKeyDown } from '@/hooks'; @@ -58,7 +59,7 @@ export const RuleModal: React.FC = ({ isOpen, onClose }) => { > - + {!!selectedItem?.type ? ( diff --git a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx index c834b2fb3..e4198e67c 100644 --- a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx +++ b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx @@ -35,15 +35,9 @@ export default function OverviewDataFlowContainer() { const positions = useMemo(() => getNodePositions({ containerWidth }), [containerWidth]); const { metrics } = useMetrics(); - const { data, filteredData, startPolling } = useComputePlatform(); + const { data, filteredData } = useComputePlatform(); const unfilteredCounts = useMemo(() => getEntityCounts({ computePlatform: data?.computePlatform }), [data]); - useEffect(() => { - // this is to start polling on component mount in an attempt to fix any initial errors with sources/destinations - if (!!data?.computePlatform.k8sActualSources.length || !!data?.computePlatform.destinations.length) startPolling(); - // only on-mount, if we include "data" this will trigger on every refetch, causing an infinite loop - }, []); - const ruleNodes = useMemo( () => buildRuleNodes({ entities: filteredData?.computePlatform.instrumentationRules || [], positions, unfilteredCounts }), [filteredData?.computePlatform.instrumentationRules, positions, unfilteredCounts], diff --git a/frontend/webapp/cypress/constants/index.ts b/frontend/webapp/cypress/constants/index.ts index 8afbf4d6e..fcc22c8b9 100644 --- a/frontend/webapp/cypress/constants/index.ts +++ b/frontend/webapp/cypress/constants/index.ts @@ -70,6 +70,9 @@ export const DATA_IDS = { TITLE: '[data-id=title]', SOURCE_TITLE: '[data-id=sourceName]', CHECKBOX: '[data-id=checkbox]', + + NOTIF_MANAGER_BUTTON: '[data-id=notif-manager-button]', + NOTIF_MANAGER_CONTENR: '[data-id=notif-manager-content]', }; export const BUTTONS = { @@ -88,6 +91,8 @@ const CYPRESS_TEST = 'Cypress Test'; export const TEXTS = { UPDATED_NAME: CYPRESS_TEST, + NO_RESOURCES: (namespace: string) => `No resources found in ${namespace} namespace.`, + SOURCE_WARN_MODAL_TITLE: 'Uninstrument 5 sources', SOURCE_WARN_MODAL_NOTE: "You're about to uninstrument the last source", DESTINATION_WARN_MODAL_TITLE: `Delete destination (${CYPRESS_TEST})`, @@ -95,5 +100,6 @@ export const TEXTS = { ACTION_WARN_MODAL_TITLE: `Delete action (${CYPRESS_TEST})`, INSTRUMENTATION_RULE_WARN_MODAL_TITLE: `Delete rule (${CYPRESS_TEST})`, - NO_RESOURCES: (namespace: string) => `No resources found in ${namespace} namespace.`, + NOTIF_SOURCES_CREATED: 'successfully added 5 sources', + NOTIF_SOURCES_DELETED: 'successfully deleted 5 sources', }; diff --git a/frontend/webapp/cypress/e2e/03-sources.cy.ts b/frontend/webapp/cypress/e2e/03-sources.cy.ts index 59f69e44b..84d3709e9 100644 --- a/frontend/webapp/cypress/e2e/03-sources.cy.ts +++ b/frontend/webapp/cypress/e2e/03-sources.cy.ts @@ -11,7 +11,7 @@ const crdName = CRD_NAMES.SOURCE; describe('Sources CRUD', () => { beforeEach(() => cy.intercept('/graphql').as('gql')); - it('Should create a CRD in the cluster', () => { + it('Should create a CRD in the cluster, and notify with SSE', () => { cy.visit(ROUTES.OVERVIEW); getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { @@ -25,7 +25,10 @@ describe('Sources CRUD', () => { cy.contains('button', BUTTONS.DONE).click(); cy.wait('@gql').then(() => { - getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 5 }); + getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 5 }, () => { + cy.get(DATA_IDS.NOTIF_MANAGER_BUTTON).click(); + cy.get(DATA_IDS.NOTIF_MANAGER_CONTENR).contains(TEXTS.NOTIF_SOURCES_CREATED).should('exist'); + }); }); }); }); @@ -55,7 +58,7 @@ describe('Sources CRUD', () => { }); }); - it('Should delete the CRD from the cluster', () => { + it('Should delete the CRD from the cluster, and notify with SSE', () => { cy.visit(ROUTES.OVERVIEW); getCrdIds({ namespace, crdName, expectedError: '', expectedLength: 5 }, () => { @@ -66,7 +69,10 @@ describe('Sources CRUD', () => { cy.get(DATA_IDS.APPROVE).click(); cy.wait('@gql').then(() => { - getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }); + getCrdIds({ namespace, crdName, expectedError: TEXTS.NO_RESOURCES(namespace), expectedLength: 0 }, () => { + cy.get(DATA_IDS.NOTIF_MANAGER_BUTTON).click(); + cy.get(DATA_IDS.NOTIF_MANAGER_CONTENR).contains(TEXTS.NOTIF_SOURCES_DELETED).should('exist'); + }); }); }); }); diff --git a/frontend/webapp/cypress/e2e/04-destinations.cy.ts b/frontend/webapp/cypress/e2e/04-destinations.cy.ts index bc4ec28f3..a510d03c7 100644 --- a/frontend/webapp/cypress/e2e/04-destinations.cy.ts +++ b/frontend/webapp/cypress/e2e/04-destinations.cy.ts @@ -71,4 +71,3 @@ describe('Destinations CRUD', () => { }); }); }); -// destination-odigos.io.dest.jaeger-chc52 diff --git a/frontend/webapp/hooks/actions/useActionCRUD.ts b/frontend/webapp/hooks/actions/useActionCRUD.ts index f75ba691a..f261ee47e 100644 --- a/frontend/webapp/hooks/actions/useActionCRUD.ts +++ b/frontend/webapp/hooks/actions/useActionCRUD.ts @@ -1,10 +1,9 @@ import { useMutation } from '@apollo/client'; import { useNotificationStore } from '@/store'; -import { useNotify } from '../notification/useNotify'; +import { ACTION, getSseTargetFromId } from '@/utils'; import { useComputePlatform } from '../compute-platform'; -import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; import { CREATE_ACTION, DELETE_ACTION, UPDATE_ACTION } from '@/graphql/mutations'; -import { OVERVIEW_ENTITY_TYPES, type ActionInput, type ActionsType, type NotificationType } from '@/types'; +import { NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES, type ActionInput, type ActionsType } from '@/types'; interface UseActionCrudParams { onSuccess?: (type: string) => void; @@ -14,10 +13,10 @@ interface UseActionCrudParams { export const useActionCRUD = (params?: UseActionCrudParams) => { const removeNotifications = useNotificationStore((store) => store.removeNotifications); const { data, refetch } = useComputePlatform(); - const notify = useNotify(); + const { addNotification } = useNotificationStore(); - const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { - notify({ + const notifyUser = (type: NOTIFICATION_TYPE, title: string, message: string, id?: string) => { + addNotification({ type, title, message, @@ -27,12 +26,12 @@ export const useActionCRUD = (params?: UseActionCrudParams) => { }; const handleError = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.ERROR, title, message, id); + notifyUser(NOTIFICATION_TYPE.ERROR, title, message, id); params?.onError?.(title); }; const handleComplete = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.SUCCESS, title, message, id); + notifyUser(NOTIFICATION_TYPE.SUCCESS, title, message, id); refetch(); params?.onSuccess?.(title); }; diff --git a/frontend/webapp/hooks/actions/useActionFormData.ts b/frontend/webapp/hooks/actions/useActionFormData.ts index 03d0ee864..61872fdb8 100644 --- a/frontend/webapp/hooks/actions/useActionFormData.ts +++ b/frontend/webapp/hooks/actions/useActionFormData.ts @@ -1,7 +1,7 @@ -import { DrawerItem } from '@/store'; -import { useGenericForm, useNotify } from '@/hooks'; -import { FORM_ALERTS, NOTIFICATION } from '@/utils'; -import type { ActionDataParsed, ActionInput } from '@/types'; +import { DrawerItem, useNotificationStore } from '@/store'; +import { FORM_ALERTS } from '@/utils'; +import { useGenericForm } from '@/hooks'; +import { NOTIFICATION_TYPE, type ActionDataParsed, type ActionInput } from '@/types'; const INITIAL: ActionInput = { // @ts-ignore (TS complains about empty string because we expect an "ActionsType", but it's fine) @@ -14,7 +14,7 @@ const INITIAL: ActionInput = { }; export function useActionFormData() { - const notify = useNotify(); + const { addNotification } = useNotificationStore(); const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL); const validateForm = (params?: { withAlert?: boolean; alertTitle?: string }) => { @@ -38,8 +38,8 @@ export function useActionFormData() { }); if (!ok && params?.withAlert) { - notify({ - type: NOTIFICATION.WARNING, + addNotification({ + type: NOTIFICATION_TYPE.WARNING, title: params.alertTitle, message: FORM_ALERTS.REQUIRED_FIELDS, }); diff --git a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts index f3835e368..919c032aa 100644 --- a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts +++ b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts @@ -1,6 +1,5 @@ -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { useQuery } from '@apollo/client'; -import { useBooleanStore } from '@/store'; import { GET_COMPUTE_PLATFORM } from '@/graphql'; import { useFilterStore } from '@/store/useFilterStore'; import { BACKEND_BOOLEAN, deriveTypeFromRule, safeJsonParse } from '@/utils'; @@ -12,30 +11,12 @@ type UseComputePlatformHook = { loading: boolean; error?: Error; refetch: () => void; - startPolling: () => Promise; }; export const useComputePlatform = (): UseComputePlatformHook => { const { data, loading, error, refetch } = useQuery(GET_COMPUTE_PLATFORM); - const { togglePolling } = useBooleanStore(); const filters = useFilterStore(); - const startPolling = useCallback(async () => { - togglePolling(true); - - let retries = 0; - const maxRetries = 5; - const retryInterval = 3 * 1000; // time in milliseconds - - while (retries < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, retryInterval)); - refetch(); - retries++; - } - - togglePolling(false); - }, [refetch, togglePolling]); - const mappedData = useMemo(() => { if (!data) return undefined; @@ -95,5 +76,5 @@ export const useComputePlatform = (): UseComputePlatformHook => { }; }, [mappedData, filters]); - return { data: mappedData, filteredData, loading, error, refetch, startPolling }; + return { data: mappedData, filteredData, loading, error, refetch }; }; diff --git a/frontend/webapp/hooks/compute-platform/useNamespace.ts b/frontend/webapp/hooks/compute-platform/useNamespace.ts index 7bf034592..fe95b9ea5 100644 --- a/frontend/webapp/hooks/compute-platform/useNamespace.ts +++ b/frontend/webapp/hooks/compute-platform/useNamespace.ts @@ -1,20 +1,19 @@ -import { NOTIFICATION } from '@/utils'; -import { useNotify } from '../notification'; +import { useNotificationStore } from '@/store'; import { useMutation, useQuery } from '@apollo/client'; import { useComputePlatform } from './useComputePlatform'; import { GET_NAMESPACES, PERSIST_NAMESPACE } from '@/graphql'; -import { ComputePlatform, PersistNamespaceItemInput } from '@/types'; +import { type ComputePlatform, NOTIFICATION_TYPE, type PersistNamespaceItemInput } from '@/types'; export const useNamespace = (namespaceName?: string, instrumentationLabeled = null as boolean | null) => { - const notify = useNotify(); + const { addNotification } = useNotificationStore(); const cp = useComputePlatform(); const handleError = (title: string, message: string) => { - notify({ type: NOTIFICATION.ERROR, title, message }); + addNotification({ type: NOTIFICATION_TYPE.ERROR, title, message }); }; const handleComplete = (title: string, message: string) => { - notify({ type: NOTIFICATION.SUCCESS, title, message }); + addNotification({ type: NOTIFICATION_TYPE.SUCCESS, title, message }); }; const { data, loading, error } = useQuery(GET_NAMESPACES, { diff --git a/frontend/webapp/hooks/destinations/useDestinationCRUD.ts b/frontend/webapp/hooks/destinations/useDestinationCRUD.ts index 2088c18cc..f30bf80cb 100644 --- a/frontend/webapp/hooks/destinations/useDestinationCRUD.ts +++ b/frontend/webapp/hooks/destinations/useDestinationCRUD.ts @@ -1,9 +1,8 @@ import { useMutation } from '@apollo/client'; import { useNotificationStore } from '@/store'; -import { useNotify } from '../notification/useNotify'; +import { ACTION, getSseTargetFromId } from '@/utils'; import { useComputePlatform } from '../compute-platform'; -import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; -import { OVERVIEW_ENTITY_TYPES, type DestinationInput, type NotificationType } from '@/types'; +import { NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES, type DestinationInput } from '@/types'; import { CREATE_DESTINATION, DELETE_DESTINATION, UPDATE_DESTINATION } from '@/graphql/mutations'; interface Params { @@ -14,10 +13,10 @@ interface Params { export const useDestinationCRUD = (params?: Params) => { const removeNotifications = useNotificationStore((store) => store.removeNotifications); const { data, refetch } = useComputePlatform(); - const notify = useNotify(); + const { addNotification } = useNotificationStore(); - const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { - notify({ + const notifyUser = (type: NOTIFICATION_TYPE, title: string, message: string, id?: string) => { + addNotification({ type, title, message, @@ -27,12 +26,12 @@ export const useDestinationCRUD = (params?: Params) => { }; const handleError = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.ERROR, title, message, id); + notifyUser(NOTIFICATION_TYPE.ERROR, title, message, id); params?.onError?.(title); }; const handleComplete = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.SUCCESS, title, message, id); + notifyUser(NOTIFICATION_TYPE.SUCCESS, title, message, id); refetch(); params?.onSuccess?.(title); }; diff --git a/frontend/webapp/hooks/destinations/useDestinationFormData.ts b/frontend/webapp/hooks/destinations/useDestinationFormData.ts index 07927d7e0..eb50c9ac2 100644 --- a/frontend/webapp/hooks/destinations/useDestinationFormData.ts +++ b/frontend/webapp/hooks/destinations/useDestinationFormData.ts @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react'; -import { DrawerItem } from '@/store'; import { useQuery } from '@apollo/client'; import { GET_DESTINATION_TYPE_DETAILS } from '@/graphql'; -import { useConnectDestinationForm, useGenericForm, useNotify } from '@/hooks'; -import { ACTION, FORM_ALERTS, NOTIFICATION, safeJsonParse } from '@/utils'; +import { DrawerItem, useNotificationStore } from '@/store'; +import { ACTION, FORM_ALERTS, safeJsonParse } from '@/utils'; +import { useConnectDestinationForm, useGenericForm } from '@/hooks'; import { type DynamicField, type DestinationDetailsResponse, @@ -12,6 +12,7 @@ import { type ActualDestination, type SupportedDestinationSignals, OVERVIEW_ENTITY_TYPES, + NOTIFICATION_TYPE, } from '@/types'; const INITIAL: DestinationInput = { @@ -28,7 +29,7 @@ const INITIAL: DestinationInput = { export function useDestinationFormData(params?: { destinationType?: string; supportedSignals?: SupportedDestinationSignals; preLoadedFields?: string | DestinationTypeItem['fields'] }) { const { destinationType, supportedSignals, preLoadedFields } = params || {}; - const notify = useNotify(); + const { addNotification } = useNotificationStore(); const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL); const { buildFormDynamicFields } = useConnectDestinationForm(); @@ -38,7 +39,13 @@ export function useDestinationFormData(params?: { destinationType?: string; supp const { data: { destinationTypeDetails } = {} } = useQuery(GET_DESTINATION_TYPE_DETAILS, { variables: { type: t }, skip: !t, - onError: (error) => notify({ type: NOTIFICATION.ERROR, title: ACTION.FETCH, message: error.message, crdType: OVERVIEW_ENTITY_TYPES.DESTINATION }), + onError: (error) => + addNotification({ + type: NOTIFICATION_TYPE.ERROR, + title: ACTION.FETCH, + message: error.message, + crdType: OVERVIEW_ENTITY_TYPES.DESTINATION, + }), }); useEffect(() => { @@ -98,8 +105,8 @@ export function useDestinationFormData(params?: { destinationType?: string; supp }); if (!ok && params?.withAlert) { - notify({ - type: NOTIFICATION.WARNING, + addNotification({ + type: NOTIFICATION_TYPE.WARNING, title: params.alertTitle, message: FORM_ALERTS.REQUIRED_FIELDS, }); diff --git a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts index 3181d3cbc..407c8961b 100644 --- a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts +++ b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts @@ -1,9 +1,8 @@ import { useMutation } from '@apollo/client'; import { useNotificationStore } from '@/store'; -import { useNotify } from '../notification/useNotify'; import { useComputePlatform } from '../compute-platform'; -import { ACTION, deriveTypeFromRule, getSseTargetFromId, NOTIFICATION } from '@/utils'; -import { OVERVIEW_ENTITY_TYPES, type InstrumentationRuleInput, type NotificationType } from '@/types'; +import { ACTION, deriveTypeFromRule, getSseTargetFromId } from '@/utils'; +import { NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES, type InstrumentationRuleInput } from '@/types'; import { CREATE_INSTRUMENTATION_RULE, UPDATE_INSTRUMENTATION_RULE, DELETE_INSTRUMENTATION_RULE } from '@/graphql/mutations'; interface Params { @@ -14,10 +13,10 @@ interface Params { export const useInstrumentationRuleCRUD = (params?: Params) => { const removeNotifications = useNotificationStore((store) => store.removeNotifications); const { data, refetch } = useComputePlatform(); - const notify = useNotify(); + const { addNotification } = useNotificationStore(); - const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { - notify({ + const notifyUser = (type: NOTIFICATION_TYPE, title: string, message: string, id?: string) => { + addNotification({ type, title, message, @@ -27,12 +26,12 @@ export const useInstrumentationRuleCRUD = (params?: Params) => { }; const handleError = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.ERROR, title, message, id); + notifyUser(NOTIFICATION_TYPE.ERROR, title, message, id); params?.onError?.(title); }; const handleComplete = (title: string, message: string, id?: string) => { - notifyUser(NOTIFICATION.SUCCESS, title, message, id); + notifyUser(NOTIFICATION_TYPE.SUCCESS, title, message, id); refetch(); params?.onSuccess?.(title); }; diff --git a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts index 6fe9b7232..20d14e7df 100644 --- a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts +++ b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleFormData.ts @@ -1,7 +1,7 @@ -import type { DrawerItem } from '@/store'; -import { useGenericForm, useNotify } from '@/hooks'; -import { FORM_ALERTS, NOTIFICATION } from '@/utils'; -import { PayloadCollectionType, type InstrumentationRuleInput, type InstrumentationRuleSpec } from '@/types'; +import { FORM_ALERTS } from '@/utils'; +import { useGenericForm } from '@/hooks'; +import { useNotificationStore, type DrawerItem } from '@/store'; +import { NOTIFICATION_TYPE, PayloadCollectionType, type InstrumentationRuleInput, type InstrumentationRuleSpec } from '@/types'; const INITIAL: InstrumentationRuleInput = { ruleName: '', @@ -18,7 +18,7 @@ const INITIAL: InstrumentationRuleInput = { }; export function useInstrumentationRuleFormData() { - const notify = useNotify(); + const { addNotification } = useNotificationStore(); const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm(INITIAL); const validateForm = (params?: { withAlert?: boolean; alertTitle?: string }) => { @@ -41,8 +41,8 @@ export function useInstrumentationRuleFormData() { }); if (!ok && params?.withAlert) { - notify({ - type: NOTIFICATION.WARNING, + addNotification({ + type: NOTIFICATION_TYPE.WARNING, title: params.alertTitle, message: FORM_ALERTS.REQUIRED_FIELDS, }); diff --git a/frontend/webapp/hooks/notification/index.ts b/frontend/webapp/hooks/notification/index.ts index 44d4bfd4e..20874d0ed 100644 --- a/frontend/webapp/hooks/notification/index.ts +++ b/frontend/webapp/hooks/notification/index.ts @@ -1,3 +1,2 @@ export * from './useClickNotif'; -export * from './useNotify'; export * from './useSSE'; diff --git a/frontend/webapp/hooks/notification/useNotify.ts b/frontend/webapp/hooks/notification/useNotify.ts deleted file mode 100644 index 910ffe07a..000000000 --- a/frontend/webapp/hooks/notification/useNotify.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useNotificationStore } from '@/store'; -import { Notification } from '@/types'; - -export const useNotify = () => { - const { addNotification } = useNotificationStore(); - - const notify = ({ - type, - title, - message, - crdType, - target, - }: { - type: Notification['type']; - title?: Notification['title']; - message?: Notification['message']; - crdType?: Notification['crdType']; - target?: Notification['target']; - }) => { - addNotification({ type, title, message, crdType, target }); - }; - - return notify; -}; diff --git a/frontend/webapp/hooks/notification/useSSE.ts b/frontend/webapp/hooks/notification/useSSE.ts index 98f67a4b5..44629d415 100644 --- a/frontend/webapp/hooks/notification/useSSE.ts +++ b/frontend/webapp/hooks/notification/useSSE.ts @@ -1,13 +1,27 @@ -import { useEffect, useRef, useState } from 'react'; -import { useNotify } from './useNotify'; -import { API, NOTIFICATION } from '@/utils'; -import { useConnectionStore } from '@/store'; - -export function useSSE() { - const notify = useNotify(); +import { useEffect, useRef } from 'react'; +import { API } from '@/utils'; +import { NOTIFICATION_TYPE } from '@/types'; +import { useComputePlatform } from '../compute-platform'; +import { type NotifyPayload, useConnectionStore, useNotificationStore } from '@/store'; + +const modifyType = (notification: NotifyPayload) => { + if (notification.title === 'Modified') { + if (notification.message?.indexOf('ProcessTerminated') === 0 || notification.message?.indexOf('NoHeartbeat') === 0 || notification.message?.indexOf('Failed') === 0) { + return NOTIFICATION_TYPE.ERROR; + } else { + return NOTIFICATION_TYPE.INFO; + } + } + + return notification.type; +}; + +export const useSSE = () => { + const { addNotification } = useNotificationStore(); const { setConnectionStore } = useConnectionStore(); + const { refetch: refetchComputePlatform } = useComputePlatform(); - const [retryCount, setRetryCount] = useState(0); + const retryCount = useRef(0); const eventBuffer = useRef({}); const maxRetries = 10; @@ -15,72 +29,61 @@ export function useSSE() { const connect = () => { const eventSource = new EventSource(API.EVENTS); - eventSource.onmessage = function (event) { - const data = JSON.parse(event.data); + eventSource.onmessage = (event) => { const key = event.data; + const data = JSON.parse(key); - const notification = { - id: Date.now(), - message: data.data, - title: data.event, + const notification: NotifyPayload = { type: data.type, - target: data.target, + title: data.event, + message: data.data, crdType: data.crdType, + target: data.target, }; + notification.type = modifyType(notification); + // Check if the event is already in the buffer if (eventBuffer.current[key] && eventBuffer.current[key].id > Date.now() - 2000) { eventBuffer.current[key] = notification; - return; } else { // Add a new event to the buffer eventBuffer.current[key] = notification; - } - // Dispatch the notification to the store - notify({ - type: eventBuffer.current[key].type, - title: eventBuffer.current[key].title, - message: eventBuffer.current[key].message, - crdType: eventBuffer.current[key].crdType, - target: eventBuffer.current[key].target, - }); + // Dispatch the notification to the store + addNotification(notification); + refetchComputePlatform(); + } // Reset retry count on successful connection - setRetryCount(0); + retryCount.current = 0; }; - eventSource.onerror = function (event) { + eventSource.onerror = (event) => { console.error('EventSource failed:', event); eventSource.close(); // Retry connection with exponential backoff if below max retries - setRetryCount((prevRetryCount) => { - if (prevRetryCount < maxRetries) { - const newRetryCount = prevRetryCount + 1; - const retryTimeout = Math.min(10000, 1000 * Math.pow(2, newRetryCount)); - - setTimeout(() => connect(), retryTimeout); - - return newRetryCount; - } else { - console.error('Max retries reached. Could not reconnect to EventSource.'); - - setConnectionStore({ - connecting: false, - active: false, - title: `Connection lost on ${new Date().toLocaleString()}`, - message: 'Please reboot the application', - }); - notify({ - type: NOTIFICATION.ERROR, - title: 'Connection Error', - message: 'Connection to the server failed. Please reboot the application.', - }); - - return prevRetryCount; - } - }); + if (retryCount.current < maxRetries) { + retryCount.current += 1; + const retryTimeout = Math.min(10000, 1000 * Math.pow(2, retryCount.current)); + + setTimeout(() => connect(), retryTimeout); + } else { + console.error('Max retries reached. Could not reconnect to EventSource.'); + + setConnectionStore({ + connecting: false, + active: false, + title: `Connection lost on ${new Date().toLocaleString()}`, + message: 'Please reboot the application', + }); + addNotification({ + type: NOTIFICATION_TYPE.ERROR, + title: 'Connection Error', + message: 'Connection to the server failed. Please reboot the application.', + }); + } }; setConnectionStore({ @@ -100,4 +103,4 @@ export function useSSE() { eventSource.close(); }; }, []); -} +}; diff --git a/frontend/webapp/hooks/sources/useSourceCRUD.ts b/frontend/webapp/hooks/sources/useSourceCRUD.ts index fc679efa7..82f30bdf7 100644 --- a/frontend/webapp/hooks/sources/useSourceCRUD.ts +++ b/frontend/webapp/hooks/sources/useSourceCRUD.ts @@ -1,10 +1,9 @@ -import { useNotify } from '../notification'; import { useMutation } from '@apollo/client'; import { useNotificationStore } from '@/store'; -import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; +import { ACTION, getSseTargetFromId } from '@/utils'; import { PERSIST_SOURCE, UPDATE_K8S_ACTUAL_SOURCE } from '@/graphql'; import { useComputePlatform, useNamespace } from '../compute-platform'; -import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type NotificationType, type PatchSourceRequestInput, type K8sActualSource } from '@/types'; +import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type PatchSourceRequestInput, type K8sActualSource, NOTIFICATION_TYPE } from '@/types'; interface Params { onSuccess?: (type: string) => void; @@ -13,12 +12,12 @@ interface Params { export const useSourceCRUD = (params?: Params) => { const removeNotifications = useNotificationStore((store) => store.removeNotifications); - const { data, startPolling } = useComputePlatform(); const { persistNamespace } = useNamespace(); - const notify = useNotify(); + const { data, refetch } = useComputePlatform(); + const { addNotification } = useNotificationStore(); - const notifyUser = (type: NotificationType, title: string, message: string, id?: WorkloadId) => { - notify({ + const notifyUser = (type: NOTIFICATION_TYPE, title: string, message: string, id?: WorkloadId) => { + addNotification({ type, title, message, @@ -28,13 +27,13 @@ export const useSourceCRUD = (params?: Params) => { }; const handleError = (title: string, message: string, id?: WorkloadId) => { - notifyUser(NOTIFICATION.ERROR, title, message, id); + notifyUser(NOTIFICATION_TYPE.ERROR, title, message, id); params?.onError?.(title); }; const handleComplete = (title: string, message: string, id?: WorkloadId) => { - notifyUser(NOTIFICATION.SUCCESS, title, message, id); - startPolling(); + notifyUser(NOTIFICATION_TYPE.SUCCESS, title, message, id); + refetch(); params?.onSuccess?.(title); }; diff --git a/frontend/webapp/reuseable-components/condition-details/index.tsx b/frontend/webapp/reuseable-components/condition-details/index.tsx index 5d3baabec..392a66c06 100644 --- a/frontend/webapp/reuseable-components/condition-details/index.tsx +++ b/frontend/webapp/reuseable-components/condition-details/index.tsx @@ -2,8 +2,8 @@ import React, { useMemo, useState } from 'react'; import Image from 'next/image'; import theme from '@/styles/theme'; import styled from 'styled-components'; -import type { Condition } from '@/types'; import { BACKEND_BOOLEAN, getStatusIcon } from '@/utils'; +import { NOTIFICATION_TYPE, type Condition } from '@/types'; import { ExtendIcon, FadeLoader, Text } from '@/reuseable-components'; interface Props { @@ -51,7 +51,7 @@ export const ConditionDetails: React.FC = ({ conditions }) => { return ( setExtend((prev) => !prev)} $hasErrors={hasErrors}>
- {loading ? : } + {loading ? : } {headerText} @@ -67,7 +67,7 @@ export const ConditionDetails: React.FC = ({ conditions }) => { {conditions.map(({ status, message }, idx) => ( - + {message} diff --git a/frontend/webapp/reuseable-components/divider/index.tsx b/frontend/webapp/reuseable-components/divider/index.tsx index 35294176b..392fbca30 100644 --- a/frontend/webapp/reuseable-components/divider/index.tsx +++ b/frontend/webapp/reuseable-components/divider/index.tsx @@ -1,11 +1,11 @@ import React from 'react'; import styled from 'styled-components'; import { hexPercentValues } from '@/styles'; -import type { NotificationType } from '@/types'; +import { NOTIFICATION_TYPE } from '@/types'; interface Props { orientation?: 'horizontal' | 'vertical'; - type?: NotificationType; // this is to apply coloring to the divider + type?: NOTIFICATION_TYPE; // this is to apply coloring to the divider thickness?: number; length?: string; margin?: string; diff --git a/frontend/webapp/reuseable-components/icon-button/index.tsx b/frontend/webapp/reuseable-components/icon-button/index.tsx index 07e803bc2..149d976c5 100644 --- a/frontend/webapp/reuseable-components/icon-button/index.tsx +++ b/frontend/webapp/reuseable-components/icon-button/index.tsx @@ -53,9 +53,9 @@ const Ping = styled.div<{ $color: Props['pingColor'] }>` } `; -export const IconButton: React.FC = ({ children, onClick, withPing, pingColor }) => { +export const IconButton: React.FC = ({ children, onClick, withPing, pingColor, ...props }) => { return ( - diff --git a/frontend/webapp/reuseable-components/modal/warning-modal/index.tsx b/frontend/webapp/reuseable-components/modal/warning-modal/index.tsx index bd8e76109..e3349a478 100644 --- a/frontend/webapp/reuseable-components/modal/warning-modal/index.tsx +++ b/frontend/webapp/reuseable-components/modal/warning-modal/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useKeyDown } from '@/hooks'; import styled from 'styled-components'; -import type { NotificationType } from '@/types'; +import { NOTIFICATION_TYPE } from '@/types'; import { Button, ButtonProps, Modal, NotificationNote, Text } from '@/reuseable-components'; interface ButtonParams { @@ -16,7 +16,7 @@ interface Props { title: string; description: string; note?: { - type: NotificationType; + type: NOTIFICATION_TYPE; title: string; message: string; }; diff --git a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx index 4414e19cc..583103e5b 100644 --- a/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx +++ b/frontend/webapp/reuseable-components/nodes-data-flow/nodes/base-node.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { getStatusIcon } from '@/utils'; import { Checkbox, DataTab } from '@/reuseable-components'; import { Handle, type Node, type NodeProps, Position } from '@xyflow/react'; -import { type ActionDataParsed, type ActualDestination, type InstrumentationRuleSpec, type K8sActualSource, NODE_TYPES, OVERVIEW_ENTITY_TYPES, STATUSES, WorkloadId } from '@/types'; +import { type ActionDataParsed, type ActualDestination, type InstrumentationRuleSpec, type K8sActualSource, NODE_TYPES, NOTIFICATION_TYPE, OVERVIEW_ENTITY_TYPES, STATUSES, WorkloadId } from '@/types'; interface Props extends NodeProps< @@ -63,7 +63,7 @@ const BaseNode: React.FC = ({ id: nodeId, data }) => { <> {/* TODO: handle instrumentation rules for sources */} {isError ? ( - + ) : // : type === 'source' && SOME_INDICATOR_THAT_THIS_IS_INSTRUMENTED ? ( ) null} diff --git a/frontend/webapp/reuseable-components/notification-note/index.tsx b/frontend/webapp/reuseable-components/notification-note/index.tsx index 8223c695a..dae9046f7 100644 --- a/frontend/webapp/reuseable-components/notification-note/index.tsx +++ b/frontend/webapp/reuseable-components/notification-note/index.tsx @@ -5,15 +5,15 @@ import { Divider } from '../divider'; import styled from 'styled-components'; import { getStatusIcon } from '@/utils'; import { progress, slide } from '@/styles'; -import type { Notification, NotificationType } from '@/types'; +import { type Notification, NOTIFICATION_TYPE } from '@/types'; interface OnCloseParams { asSeen: boolean; } -interface NotificationProps { +interface Props { id?: string; - type: NotificationType; + type: NOTIFICATION_TYPE; title?: Notification['title']; message?: Notification['message']; action?: { @@ -37,7 +37,7 @@ const Container = styled.div<{ $isLeaving?: boolean }>` } `; -const DurationAnimation = styled.div<{ $type: NotificationType }>` +const DurationAnimation = styled.div<{ $type: Props['type'] }>` position: absolute; bottom: -1px; left: 0; @@ -49,7 +49,7 @@ const DurationAnimation = styled.div<{ $type: NotificationType }>` animation: ${progress.out} ${TOAST_DURATION - TRANSITION_DURATION}ms forwards; `; -const Content = styled.div<{ $type: NotificationType }>` +const Content = styled.div<{ $type: Props['type'] }>` display: flex; align-items: center; flex: 1; @@ -65,12 +65,12 @@ const TextWrapper = styled.div` height: 12px; `; -const Title = styled(Text)<{ $type: NotificationType }>` +const Title = styled(Text)<{ $type: Props['type'] }>` font-size: 14px; color: ${({ $type, theme }) => theme.text[$type]}; `; -const Message = styled(Text)<{ $type: NotificationType }>` +const Message = styled(Text)<{ $type: Props['type'] }>` font-size: 12px; color: ${({ $type, theme }) => theme.text[$type]}; `; @@ -105,7 +105,7 @@ const CloseButton = styled(Image)` } `; -export const NotificationNote: React.FC = ({ type, title, message, action, onClose, style }) => { +export const NotificationNote: React.FC = ({ type, title, message, action, onClose, style }) => { // These are for handling transitions: // isEntering - to stop the progress bar from rendering before the toast is fully slide-in // isLeaving - to trigger the slide-out animation diff --git a/frontend/webapp/reuseable-components/status/index.tsx b/frontend/webapp/reuseable-components/status/index.tsx index c9c891987..0563a70dc 100644 --- a/frontend/webapp/reuseable-components/status/index.tsx +++ b/frontend/webapp/reuseable-components/status/index.tsx @@ -1,7 +1,8 @@ import React from 'react'; import Image from 'next/image'; -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; import { getStatusIcon } from '@/utils'; +import { NOTIFICATION_TYPE } from '@/types'; import { Divider, Text } from '@/reuseable-components'; import theme, { hexPercentValues } from '@/styles/theme'; @@ -76,7 +77,12 @@ export const Status: React.FC = ({ title, subtitle, size = 12, fami {withIcon && ( {/* TODO: SVG to JSX */} - status + status )} @@ -90,7 +96,7 @@ export const Status: React.FC = ({ title, subtitle, size = 12, fami {!!subtitle && ( - + {subtitle} diff --git a/frontend/webapp/store/index.ts b/frontend/webapp/store/index.ts index b35649a7d..017c41823 100644 --- a/frontend/webapp/store/index.ts +++ b/frontend/webapp/store/index.ts @@ -1,5 +1,4 @@ export * from './useAppStore'; -export * from './useBooleanStore'; export * from './useConnectionStore'; export * from './useDrawerStore'; export * from './useModalStore'; diff --git a/frontend/webapp/store/useBooleanStore.ts b/frontend/webapp/store/useBooleanStore.ts deleted file mode 100644 index 0a83796f9..000000000 --- a/frontend/webapp/store/useBooleanStore.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { create } from 'zustand'; - -interface StoreState { - isPolling: boolean; - togglePolling: (bool?: boolean) => void; -} - -export const useBooleanStore = create((set) => ({ - isPolling: false, - togglePolling: (bool) => set(({ isPolling }) => ({ isPolling: bool ?? !isPolling })), -})); diff --git a/frontend/webapp/store/useNotificationStore.ts b/frontend/webapp/store/useNotificationStore.ts index 273371e9b..d16485b3a 100644 --- a/frontend/webapp/store/useNotificationStore.ts +++ b/frontend/webapp/store/useNotificationStore.ts @@ -1,9 +1,11 @@ import { create } from 'zustand'; import type { Notification } from '@/types'; +export type NotifyPayload = Omit; + interface StoreState { notifications: Notification[]; - addNotification: (notif: { type: Notification['type']; title: Notification['title']; message: Notification['message']; crdType: Notification['crdType']; target: Notification['target'] }) => void; + addNotification: (notif: NotifyPayload) => void; markAsDismissed: (id: string) => void; markAsSeen: (id: string) => void; removeNotification: (id: string) => void; diff --git a/frontend/webapp/types/common.ts b/frontend/webapp/types/common.ts index 78bdec9a5..0cd5db4e9 100644 --- a/frontend/webapp/types/common.ts +++ b/frontend/webapp/types/common.ts @@ -11,11 +11,17 @@ export interface Condition { lastTransitionTime: string; } -export type NotificationType = 'warning' | 'error' | 'success' | 'info' | 'default'; +export enum NOTIFICATION_TYPE { + WARNING = 'warning', + ERROR = 'error', + SUCCESS = 'success', + INFO = 'info', + DEFAULT = 'default', +} export interface Notification { id: string; - type: NotificationType; + type: NOTIFICATION_TYPE; title?: string; message?: string; crdType?: string; diff --git a/frontend/webapp/utils/constants/string.tsx b/frontend/webapp/utils/constants/string.tsx index 4ccde2260..8ebdd825f 100644 --- a/frontend/webapp/utils/constants/string.tsx +++ b/frontend/webapp/utils/constants/string.tsx @@ -1,5 +1,3 @@ -import type { NotificationType } from '@/types'; - export const SETUP = { MONITORS: { LOGS: 'Logs', @@ -39,16 +37,6 @@ export const FORM_ALERTS = { CANNOT_EDIT_RULE: 'Cannot edit instrumentation rule of this type', }; -export const NOTIFICATION: { - [key: string]: NotificationType; -} = { - ERROR: 'error', - SUCCESS: 'success', - WARNING: 'warning', - INFO: 'info', - DEFAULT: 'default', -}; - export const BACKEND_BOOLEAN = { FALSE: 'False', TRUE: 'True', diff --git a/frontend/webapp/utils/functions/icons/get-status-icon/index.ts b/frontend/webapp/utils/functions/icons/get-status-icon/index.ts index 8f7c8d96d..50f15c457 100644 --- a/frontend/webapp/utils/functions/icons/get-status-icon/index.ts +++ b/frontend/webapp/utils/functions/icons/get-status-icon/index.ts @@ -1,18 +1,18 @@ -import { type NotificationType } from '@/types'; +import { NOTIFICATION_TYPE } from '@/types'; const BRAND_ICON = '/brand/odigos-icon.svg'; -export const getStatusIcon = (status?: NotificationType) => { +export const getStatusIcon = (status?: NOTIFICATION_TYPE) => { if (!status) return BRAND_ICON; switch (status) { - case 'success': + case NOTIFICATION_TYPE.SUCCESS: return '/icons/notification/success-icon.svg'; - case 'error': + case NOTIFICATION_TYPE.ERROR: return '/icons/notification/error-icon2.svg'; - case 'warning': + case NOTIFICATION_TYPE.WARNING: return '/icons/notification/warning-icon2.svg'; - case 'info': + case NOTIFICATION_TYPE.INFO: return '/icons/common/info.svg'; default: return BRAND_ICON;