Skip to content

Commit

Permalink
Merge pull request #732
Browse files Browse the repository at this point in the history
* refactor(28974): refactor the group metadata editor

* feat(28974): add list of nodes in the group

* refactor(28974): refactor event log to allow fine-grained configurati…

* refactor(28974): export the adapter status component

* refactor(28974): change the rendering options for unknown status

* refactor(28974): change the option for the local event log

* feat(28974): redesign the group side panel

* feat(28974): update translations

* fix(28974): fix bug with undefined filtered node

* refactor(28974): fix pagination bar

* refactor(28974): fix action toolbars

* refactor(28974): fix translations

* teat(28974): fix teats

* chore(28974): a bit of cleaning

* refactor(28974): fix translations

* test(28974): add tests

* fix(28974): fix rendering of node type

* test(28974): add tests

* test(28974): add tests

* fix(28974): fix block

* fix(28974): fix the magic code

* fix(28974): fix a bug with the metrics container

* test(28974): fix tests
  • Loading branch information
vanch3d authored Jan 10, 2025
1 parent 757254b commit bcd5dfb
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('PaginatedTable', () => {
cy.get('th').should('have.length', 2)
cy.get('th').eq(0).should('contain.text', 'item')
cy.get('th').eq(1).should('contain.text', 'value')
cy.get('tr').should('have.length', 10 + 1)
cy.get('tr').should('have.length', 5 + 1)

cy.get('[aria-label="Go to the first page"]').should('be.visible')
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const PaginatedTable = <T,>({
const table = useReactTable({
data: data,
columns,
initialState: { pagination: { pageSize: 5 } },
state: {
columnFilters,
globalFilter,
Expand Down
23 changes: 23 additions & 0 deletions hivemq-edge/src/frontend/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,29 @@
"ungroup": "Ungroup"
},
"editor": {
"tabs": {
"config": "Configuration",
"events": "Events",
"metrics": "Metrics"
},
"content": {
"header": {
"type": "Type",
"status": "Status",
"id": "Id",
"actions": "Actions"
},
"actions": {
"ungroup": "Remove from group",
"startAll": "Start all",
"stopAll": "Stop all",
"restartAll": "Restart all"
}
},
"eventLog": {
"header": "The 5 most recent events for devices in the group",
"showMore": "Show more in the Event Log"
},
"title": "Configure the group",
"input-title": "Title",
"input-color": "Group colour",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ describe('EventLogTable', () => {
identifier: { identifier: 'EVENT-0', type: TypeIdentifier.type.EVENT },
} as Partial<Event>)

cy.getByAriaLabel('View details of event').eq(7).click()
cy.getByAriaLabel('View details of event').eq(4).click()

cy.get('@onOpen').should('have.been.calledWithMatch', {
identifier: { identifier: 'EVENT-7', type: TypeIdentifier.type.EVENT },
identifier: { identifier: 'EVENT-4', type: TypeIdentifier.type.EVENT },
} as Partial<Event>)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,34 @@ import SeverityBadge from '../SeverityBadge.tsx'

interface EventLogTableProps {
onOpen?: (t: Event) => void
globalSourceFilter?: string
globalSourceFilter?: string[]
maxEvents?: number
variant?: 'full' | 'summary'
isSingleSource?: boolean
}

const EventLogTable: FC<EventLogTableProps> = ({ onOpen, globalSourceFilter, variant = 'full' }) => {
const EventLogTable: FC<EventLogTableProps> = ({
onOpen,
globalSourceFilter,
variant = 'full',
maxEvents = 5,
isSingleSource = false,
}) => {
const { t } = useTranslation()
const { data, isLoading, isFetching, error, refetch } = useGetEvents()

const safeData = useMemo<Event[]>(() => {
if (!data || !data?.items) return [...mockEdgeEvent(5)]
if (globalSourceFilter) {
return data.items.filter((e: Event) => e.source?.identifier === globalSourceFilter).slice(0, 5)
return data.items
.filter((e: Event) => globalSourceFilter.includes(e.source?.identifier || ''))
.slice(0, maxEvents)
}

return data.items
}, [data, globalSourceFilter])
}, [data, globalSourceFilter, maxEvents])

const columns = useMemo<ColumnDef<Event>[]>(() => {
const allColumns = useMemo<ColumnDef<Event>[]>(() => {
return [
{
accessorKey: 'identifier.identifier',
Expand Down Expand Up @@ -109,6 +119,13 @@ const EventLogTable: FC<EventLogTableProps> = ({ onOpen, globalSourceFilter, var
]
}, [isLoading, onOpen, t])

const displayColumns = useMemo(() => {
const [, createdColumn, severityColumn, idColumn, messageColumn] = allColumns
if (variant === 'full') return allColumns
if (isSingleSource) return [createdColumn, severityColumn, messageColumn]
else return [createdColumn, idColumn, severityColumn, messageColumn]
}, [allColumns, isSingleSource, variant])

if (error) {
return (
<Box mt="20%" mx="20%" alignItems="center">
Expand All @@ -120,9 +137,6 @@ const EventLogTable: FC<EventLogTableProps> = ({ onOpen, globalSourceFilter, var
)
}

// TODO[NVL] Not the best approach; destructure within memo
const [, a, b, , c] = columns

return (
<>
{variant === 'full' && (
Expand All @@ -142,8 +156,9 @@ const EventLogTable: FC<EventLogTableProps> = ({ onOpen, globalSourceFilter, var
<PaginatedTable<Event>
aria-label={t('eventLog.title')}
data={safeData}
columns={variant === 'full' ? columns : [a, b, c]}
enablePagination={variant === 'full'}
columns={displayColumns}
enablePaginationGoTo={variant === 'full'}
enablePaginationSizes={variant === 'full'}
enableColumnFilters={variant === 'full'}
// getRowStyles={(row) => {
// return { backgroundColor: theme.colors.blue[50] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AdapterStatusContainer } from '@/modules/ProtocolAdapters/components/adapters/AdapterStatusContainer.tsx'
import { mockAdapterConnectionStatus } from '@/api/hooks/useConnection/__handlers__'
import { MOCK_ADAPTER_ID } from '@/__test-utils__/mocks.ts'

describe('AdapterStatusContainer', () => {
beforeEach(() => {
cy.viewport(800, 900)
cy.intercept('api/v1/management/protocol-adapters/status', { items: [mockAdapterConnectionStatus] }).as('getStatus')
})

it('should render properly an existing adapter', () => {
cy.mountWithProviders(<AdapterStatusContainer id={MOCK_ADAPTER_ID} />)
cy.getByTestId('connection-status').should('have.text', 'Connected')
})

it('should render unknown if not an adapter', () => {
cy.mountWithProviders(<AdapterStatusContainer id="not-an-adapter" />)
cy.getByTestId('connection-status').should('have.text', 'Unknown')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FC } from 'react'
import { useGetAdaptersStatus } from '@/api/hooks/useConnection/useGetAdaptersStatus.ts'
import { ConnectionStatusBadge } from '@/components/ConnectionStatusBadge'

export const AdapterStatusContainer: FC<{ id: string }> = ({ id }) => {
const { data: connections } = useGetAdaptersStatus()

const connection = connections?.items?.find((e) => e.id === id)

return <ConnectionStatusBadge status={connection} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { useLocation, useNavigate } from 'react-router-dom'
import { Adapter, ApiError, ProtocolAdapter } from '@/api/__generated__'
import { useListProtocolAdapters } from '@/api/hooks/useProtocolAdapters/useListProtocolAdapters.ts'
import { useDeleteProtocolAdapter } from '@/api/hooks/useProtocolAdapters/useDeleteProtocolAdapter.ts'
import { useGetAdaptersStatus } from '@/api/hooks/useConnection/useGetAdaptersStatus.ts'
import { ProblemDetails } from '@/api/types/http-problem-details.ts'
import { useGetAdapterTypes } from '@/api/hooks/useProtocolAdapters/useGetAdapterTypes.ts'
import { mockAdapter } from '@/api/hooks/useProtocolAdapters/__handlers__'
Expand All @@ -17,7 +16,6 @@ import AdapterEmptyLogo from '@/assets/app/adaptor-empty.svg'

import ErrorMessage from '@/components/ErrorMessage.tsx'
import WarningMessage from '@/components/WarningMessage.tsx'
import { ConnectionStatusBadge } from '@/components/ConnectionStatusBadge'
import ConfirmationDialog from '@/components/Modal/ConfirmationDialog.tsx'
import PaginatedTable from '@/components/PaginatedTable/PaginatedTable.tsx'
import { WorkspaceIcon } from '@/components/Icons/TopicIcon.tsx'
Expand All @@ -36,17 +34,10 @@ import { useEdgeToast } from '@/hooks/useEdgeToast/useEdgeToast.tsx'
import AdapterActionMenu from '../adapters/AdapterActionMenu.tsx'
import { compareStatus } from '../../utils/pagination-utils.ts'
import IconButton from '@/components/Chakra/IconButton.tsx'
import { AdapterStatusContainer } from '@/modules/ProtocolAdapters/components/adapters/AdapterStatusContainer.tsx'

const DEFAULT_PER_PAGE = 10

const AdapterStatusContainer: FC<{ id: string }> = ({ id }) => {
const { data: connections } = useGetAdaptersStatus()

const connection = connections?.items?.find((e) => e.id === id)

return <ConnectionStatusBadge status={connection} />
}

const AdapterTypeContainer: FC<ProtocolAdapter> = (adapter) => {
return (
<HStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { theme as baseTheme } from '@chakra-ui/react'

const colors = {
status: {
unknown: baseTheme.colors.gray,
error: baseTheme.colors.red,
connected: baseTheme.colors.green,
disconnected: baseTheme.colors.orange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { MOCK_METRICS } from '@/api/hooks/useGetMetrics/__handlers__'
import { MetricList } from '@/api/__generated__'
import { MOCK_NODE_ADAPTER } from '@/__test-utils__/react-flow/nodes.ts'
import { MOCK_ADAPTER_ID, MOCK_ADAPTER_ID2 } from '@/__test-utils__/mocks.ts'
import { mockEdgeEvent } from '@/api/hooks/useEvents/__handlers__'

const mockNode: Node<Group> = {
position: { x: 0, y: 0 },
Expand Down Expand Up @@ -37,7 +38,7 @@ describe('GroupPropertyDrawer', () => {
cy.intercept('/api/v1/metrics/**', []).as('getMetricForX')
})

it('should render properly', () => {
it('should render the minimal metrics properly', () => {
const onClose = cy.stub().as('onClose')
const onEditEntity = cy.stub()
cy.mountWithProviders(
Expand Down Expand Up @@ -67,6 +68,47 @@ describe('GroupPropertyDrawer', () => {
cy.getByTestId('metrics-toggle').should('be.visible')
})

it.only('should render the full config tabs properly', () => {
cy.intercept('/api/v1/management/events?*', { items: [...mockEdgeEvent(150)] })
const onClose = cy.stub().as('onClose')
const onEditEntity = cy.stub()
cy.mountWithProviders(
<GroupPropertyDrawer
showConfig
nodeId="adapter@fgffgf"
nodes={mockNodes}
selectedNode={mockNode}
isOpen={true}
onClose={onClose}
onEditEntity={onEditEntity}
/>
)

cy.wait('@getMetricForX')

// check the panel header
cy.getByTestId('group-panel-title').should('contain.text', 'Group Overview')

// check the panel control
cy.get('@onClose').should('not.have.been.called')
cy.getByAriaLabel('Close').click()
cy.get('@onClose').should('have.been.calledOnce')

// check the panel tabs
cy.get('[role="tablist"] [role="tab"]').should('have.length', 3)
cy.get('[role="tablist"] [role="tab"]').eq(0).should('have.text', 'Configuration')
cy.get('[role="tablist"] [role="tab"]').eq(1).should('have.text', 'Events')
cy.get('[role="tablist"] [role="tab"]').eq(2).should('have.text', 'Metrics')

cy.get('[role="tablist"] + div > [role="tabpanel"]').should('have.length', 3)
cy.get('[role="tablist"] + div > [role="tabpanel"]').eq(0).should('not.have.attr', 'hidden')
cy.get('[role="tablist"] + div > [role="tabpanel"]').eq(1).should('have.attr', 'hidden')
cy.get('[role="tablist"] + div > [role="tabpanel"]').eq(2).should('have.attr', 'hidden')

cy.getByTestId('group-metadata-header').should('be.visible')
cy.getByTestId('group-content-header').should('be.visible')
})

it('should be accessible', () => {
cy.injectAxe()
cy.mountWithProviders(
Expand Down
Loading

0 comments on commit bcd5dfb

Please sign in to comment.