Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(29319): Add notification of not writable config #769

Merged
merged 8 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ export const MOCK_CAPABILITY_DATAHUB: Capability = {
'This enables HiveMQ Edge to make use of the HiveMQ Data Hub. This includes validation and transformation of data.',
}

export const MOCK_CAPABILITY_WRITEABLE_CONFIG: Capability = {
id: 'config-writeable',
displayName: 'Config can be manipulated via the REST API',
description: 'Changes to the configuration made via the REST API are persisted back into the config.xml.',
}

export const MOCK_CAPABILITY_DUMMY: Capability = {
id: 'edge',
displayName: 'This is a test capability',
Expand All @@ -150,3 +156,9 @@ export const handlers = [
return HttpResponse.json<CapabilityList>(MOCK_CAPABILITIES, { status: 200 })
}),
]

export const handlerCapabilities = (source: CapabilityList) => [
http.get('**/frontend/capabilities', () => {
return HttpResponse.json<CapabilityList>(source, { status: 200 })
}),
]
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import type { Capability } from '@/api/__generated__'

import { useGetCapabilities } from './useGetCapabilities.ts'

/**
* Another nonsensical backend magic code that needs to be duplicated (and therefore disconnected) in the frontend
* We have a single source of truth with OpenAPI; can we finally just use it?
*/
export enum CAPABILITY {
PERSISTENCE = 'mqtt-persistence',
DATAHUB = 'data-hub',
BIDIRECTIONAL_ADAPTER = 'bi-directional protocol adapters',
CONTROL_PLANE = 'control-plane-connectivity',
WRITEABLE_CONFIG = 'config-writeable',
}

export const useGetCapability = (id: string) => {
Expand Down
6 changes: 6 additions & 0 deletions hivemq-edge/src/frontend/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
"status_ERROR": "Error"
}
},
"capabilities": {
"WRITEABLE_CONFIG": {
"title": "Config cannot be manipulated via the web app",
"description": "Changes to the configuration made via the web app will NOT be persisted back into the config.xml. The requests will fail with an error message highlighting this situation."
}
},
"navigation": {
"mainPage": "Main content",
"gateway": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { act, renderHook, waitFor } from '@testing-library/react'
import { server } from '@/__test-utils__/msw/mockServer.ts'
import { SimpleWrapper as wrapper } from '@/__test-utils__/hooks/SimpleWrapper.tsx'

import { handlers as frontendHandler } from '@/api/hooks/useFrontendServices/__handlers__'
import {
handlerCapabilities,
handlers as frontendHandler,
MOCK_CAPABILITY_WRITEABLE_CONFIG,
} from '@/api/hooks/useFrontendServices/__handlers__'
import { handlers as gitHubHandler } from '@/api/hooks/useGitHub/__handlers__'

import { useGetManagedNotifications } from './useGetManagedNotifications.tsx'
Expand All @@ -29,7 +33,7 @@ describe('useGetManagedNotifications', () => {
expect(result.current.isSuccess).toBeTruthy()
})

expect(result.current.notifications).toHaveLength(2)
expect(result.current.notifications).toHaveLength(3)
expect(result.current.readNotifications).toHaveLength(0)
})

Expand All @@ -40,14 +44,45 @@ describe('useGetManagedNotifications', () => {
expect(result.current.isSuccess).toBeTruthy()
})

expect(result.current.notifications).toHaveLength(2)
expect(result.current.notifications).toHaveLength(3)
expect(result.current.readNotifications).toHaveLength(0)

// close the first notification
act(() => {
result.current.notifications[0].onCloseComplete?.()
})

expect(result.current.notifications).toHaveLength(2)
expect(result.current.readNotifications).toHaveLength(1)
expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')

// close the first notification
act(() => {
result.current.notifications[0].onCloseComplete?.()
})

expect(result.current.notifications).toHaveLength(2)
expect(result.current.readNotifications).toHaveLength(2)
expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
expect(result.current.readNotifications).toContainEqual('config-writeable')
})

it('should handle config-writeable', async () => {
server.use(...handlerCapabilities({ items: [MOCK_CAPABILITY_WRITEABLE_CONFIG] }))

const { result } = renderHook(useGetManagedNotifications, { wrapper })

await waitFor(() => {
expect(result.current.isSuccess).toBeTruthy()
})

expect(result.current.notifications).toHaveLength(2)

// close the first notification
act(() => {
result.current.notifications[0].onCloseComplete?.()
})

expect(result.current.notifications).toHaveLength(1)
expect(result.current.readNotifications).toHaveLength(1)
expect(result.current.readNotifications).toContainEqual('Default Credentials Need Changing!')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { ExternalLinkIcon } from '@chakra-ui/icons'
import { useGetReleases } from '@/api/hooks/useGitHub/useGetReleases.ts'
import { useGetNotifications } from '@/api/hooks/useFrontendServices/useGetNotifications.ts'
import { useGetConfiguration } from '@/api/hooks/useFrontendServices/useGetConfiguration.ts'
import { CAPABILITY, useGetCapability } from '@/api/hooks/useFrontendServices/useGetCapability.ts'

export const useGetManagedNotifications = () => {
const { t } = useTranslation()
const { data: configuration } = useGetConfiguration()
const { data: releases, isSuccess: isReleasesSuccess } = useGetReleases()
const { data: notification, isSuccess: isNotificationsSuccess } = useGetNotifications()
const isWritableConfig = useGetCapability(CAPABILITY.WRITEABLE_CONFIG)
const [readNotifications, setReadNotifications] = useState<string[]>([])
const [skip] = useLocalStorage<string[]>('edge.notifications', [])

Expand Down Expand Up @@ -51,6 +53,21 @@ export const useGetManagedNotifications = () => {
list.push(...toasts)
}

if (!isWritableConfig && !skip.includes(CAPABILITY.WRITEABLE_CONFIG)) {
// TODO[EDGE] The important feature is when the config is NOT writable (API request will fail)
// The question is whether undefined (because it's not found) and undefined (because it is not supported)
// have the same effect

list.push({
...defaults,
id: CAPABILITY.WRITEABLE_CONFIG,
status: 'warning',
title: <Text>{t('capabilities.WRITEABLE_CONFIG.title')} </Text>,
description: <Text>{t('capabilities.WRITEABLE_CONFIG.description')} </Text>,
onCloseComplete: () => handleReadNotification(CAPABILITY.WRITEABLE_CONFIG),
})
}

if (configuration && releases && releases.length > 0) {
const { name, html_url } = releases[0]
const currentVersion = configuration.environment?.properties?.version
Expand All @@ -77,7 +94,7 @@ export const useGetManagedNotifications = () => {
}

return list
}, [notification?.items, configuration, releases, readNotifications, skip, t])
}, [notification?.items, isWritableConfig, skip, configuration, releases, readNotifications, t])

return { notifications, isSuccess: isNotificationsSuccess && isReleasesSuccess, readNotifications }
}
Loading