Skip to content

Commit

Permalink
progress on UI: SNMP version added to form + test improvements + depl…
Browse files Browse the repository at this point in the history
…oyment to GitHub pages
  • Loading branch information
mrab-sonalake committed Jul 27, 2022
1 parent 83234e4 commit 6a38840
Show file tree
Hide file tree
Showing 33 changed files with 569 additions and 378 deletions.
20 changes: 8 additions & 12 deletions jest-setup.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import '@testing-library/jest-dom'

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
jest.mock('@tanstack/react-table', () => ({
getCoreRowModel: jest.fn(),
getSortedRowModel: jest.fn(),
useReactTable: jest.fn().mockImplementation(() => ({
getSelectedRowModel: jest.fn().mockImplementation(() => ({ flatRows: [] })),
getHeaderGroups: jest.fn().mockImplementation(() => []),
getRowModel: jest.fn().mockImplementation(() => ({ rows: [] })),
})),
})
}))
24 changes: 14 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
"version": "0.0.1",
"main": "index.js",
"repository": "git@github.com:sonalake/snmp-sim-ui.git",
"homepage": "https://sonalake.github.io/snmp-sim-ui",
"license": "Apache-2.0",
"scripts": {
"clean": "rimraf bundle & rimraf dist",
"build": "./node_modules/.bin/webpack --env NODE_ENV=production --mode production",
"predeploy": "yarn build",
"deploy": "gh-pages -d build",
"start": "./node_modules/.bin/webpack serve --mode development --hot --history-api-fallback",
"test": "jest"
},
"dependencies": {
"@tanstack/match-sorter-utils": "^8.1.1",
"@tanstack/react-table": "^8.2.6",
"@tanstack/react-table": "^8.3.3",
"axios": "^0.27.2",
"flowbite": "^1.4.7",
"flowbite": "^1.5.1",
"flowbite-react": "^0.1.3",
"formik": "^2.2.9",
"identity-obj-proxy": "^3.0.0",
Expand All @@ -23,27 +26,27 @@
"react-icons": "^4.4.0",
"react-router": "6.3.0",
"react-router-dom": "6.3.0",
"react-toastify": "^9.0.5"
"react-toastify": "^9.0.7"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/jest": "^28.1.5",
"@types/jest": "^28.1.6",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-icons": "^3.0.0",
"@types/react-router": "^5.1.18",
"@types/react-router-dom": "^5.3.3",
"@types/tailwindcss": "^3.1.0",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"autoprefixer": "^10.4.7",
"axios-mock-adapter": "^1.21.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.1",
"eslint": "^8.19.0",
"eslint": "^8.20.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^26.6.0",
Expand All @@ -52,7 +55,8 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^5.5.1",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.12",
"fork-ts-checker-webpack-plugin": "^7.2.13",
"gh-pages": "^4.0.0",
"html-webpack-plugin": "^5.5.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
Expand All @@ -66,11 +70,11 @@
"rimraf": "^3.0.2",
"style-loader": "^3.3.1",
"tailwindcss": "^3.1.6",
"ts-jest": "^28.0.6",
"ts-jest": "^28.0.7",
"ts-loader": "^9.3.1",
"tslib": "^2.4.0",
"typescript": "^4.7.4",
"webpack": "^5.73.0",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.8.0"
Expand Down
59 changes: 35 additions & 24 deletions src/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import {
SortingState,
useReactTable,
} from '@tanstack/react-table'
import { Table } from 'flowbite-react'
import { Card, Table, TextInput } from 'flowbite-react'
import React, { FC, useEffect, useState } from 'react'
import { AiOutlineCaretDown, AiOutlineCaretUp, AiOutlineFilter } from 'react-icons/ai'
import { toast } from 'react-toastify'
import { Agent, Device } from '../../models'
import { Alert } from '../Alert/Alert'
import { DataTableCheckbox } from './DataTableCheckbox/DataTableCheckbox'

interface Props<T extends Agent | Device> {
Expand All @@ -25,6 +23,7 @@ interface Props<T extends Agent | Device> {
export const DataTable: FC<Props<Agent | Device>> = ({ data, columns, isSelectable, onSelection }) => {
const [sorting, setSorting] = useState<SortingState>([])
const [rowSelection, setRowSelection] = useState({})
const [filter, setFilter] = useState('')

const table = useReactTable({
data,
Expand Down Expand Up @@ -61,29 +60,41 @@ export const DataTable: FC<Props<Agent | Device>> = ({ data, columns, isSelectab
headerGroup.headers.map((header) => (
<Table.HeadCell key={header.id} colSpan={header.colSpan}>
{!header.isPlaceholder && (
<div className="flex flex-row gap-1 items-center">
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none flex flex-row gap-2'
: 'flex flex-row justify-between gap-2',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{
<div className="flex flex-col relative" style={{ minWidth: '5vw' }}>
<div className="flex gap-1 items-center">
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none flex gap-2'
: 'flex justify-between gap-2',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{
asc: <AiOutlineCaretUp />,
desc: <AiOutlineCaretDown />,
}[header.column.getIsSorted() as string]
}
</div>
{
asc: <AiOutlineCaretUp />,
desc: <AiOutlineCaretDown />,
}[header.column.getIsSorted() as string]
}
</div>

{header.column.columnDef.header !== 'Actions' && (
<AiOutlineFilter
className="cursor-pointer"
onClick={() => toast(<Alert color="info" message="Filtering - to be implemented" />)}
/>
{header.column.columnDef.header !== 'Actions' && (
<AiOutlineFilter
className="cursor-pointer"
onClick={() =>
filter === header.column.columnDef.header
? setFilter('')
: setFilter(header.column.columnDef.header as string)
}
/>
)}
</div>
{filter === header.column.columnDef.header && (
<Card style={{ position: 'absolute', top: 20, left: 0, width: '20vw' }}>
<p>Filter by {header.column.columnDef.header} - to be implemented</p>
<TextInput />
</Card>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { screen } from '@testing-library/react'
import React from 'react'
import { customRender } from '../../../utils/testUtils/testUtils'
import { DataTableCheckbox } from './DataTableCheckbox'

describe('DataTableCheckbox', () => {
it(`should render the component`, async () => {
customRender(<DataTableCheckbox data-testid="test" />)

expect(screen.getByTestId('test')).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { HTMLProps, useEffect, useRef } from 'react'
import React, { FC, HTMLProps, useEffect, useRef } from 'react'

export const DataTableCheckbox = ({
export const DataTableCheckbox: FC<{ indeterminate?: boolean } & HTMLProps<HTMLInputElement>> = ({
indeterminate,
className = '',
...rest
}: { indeterminate?: boolean } & HTMLProps<HTMLInputElement>) => {
}) => {
const ref = useRef<HTMLInputElement>(null)

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions src/components/DataTable/tableColumns/devicesColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const devicesColumns: DevicesColumns = [
},
{
header: 'State',
accessorFn: (row) => row.snmp_host === '127.0.0.1',
cell: ({ row }) => {
const isMockActive = row.original.snmp_host === '127.0.0.1'

Expand Down
9 changes: 9 additions & 0 deletions src/components/DataTable/tableColumns/handleResource.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { getOperationProperties } from './handleResource'

describe('getOperationProperties', () => {
it(`should generate the correct operation properties`, async () => {
const messages = getOperationProperties({ resource: 'agents' })

expect(messages.post).toStrictEqual({ url: `/api/agents`, message: `Agent created!` })
})
})
2 changes: 1 addition & 1 deletion src/components/DataTable/tableColumns/handleResource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { toast } from 'react-toastify'
import { Alert } from '../..'
import { Agent, Device } from '../../../models'

const getOperationProperties = (config: { id?: string; resource: 'agents' | 'devices' }) => {
export const getOperationProperties = (config: { id?: string; resource: 'agents' | 'devices' }) => {
const { id, resource } = config

const messageResource = resource === 'agents' ? 'Agent' : 'Device'
Expand Down
18 changes: 8 additions & 10 deletions src/components/ErrorBoundary/ErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import React from 'react'
import { customRender } from '../../utils/testUtils/testUtils'
import { ErrorBoundary } from './ErrorBoundary'

describe('ErrorBoundary', () => {
it.skip(`should render error boundary component when there is an error`, async () => {
// This is needed for suppressing the testError appearing as uncaught
const mock = jest.spyOn(console, 'error').mockImplementation(() => null)
const testError = 'testError'

const testError = 'testError'
const ProblemChild = () => {
throw new Error(testError)
}

const ProblemChild = () => {
throw new Error(testError)
}
describe('ErrorBoundary', () => {
it(`should render error boundary component when there is an error`, async () => {
// This is needed for suppressing the testError appearing as uncaught
jest.spyOn(console, 'error').mockImplementation(() => null)

customRender(
<ErrorBoundary>
Expand All @@ -23,7 +23,5 @@ describe('ErrorBoundary', () => {
expect(await screen.findByText(testError)).toBeInTheDocument()
expect(await screen.findByText('Error')).toBeInTheDocument()
expect(await screen.findByText('Refresh')).toBeInTheDocument()

mock.mockRestore()
})
})
4 changes: 2 additions & 2 deletions src/components/ErrorBoundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class ErrorBoundary extends Component<Props, State> {
hasError: false,
}

public static getDerivedStateFromError(_: Error): State {
return { error: null, hasError: true }
public static getDerivedStateFromError(error: Error): State {
return { error, hasError: true }
}

public componentDidCatch(error: Error) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/Form/AgentSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Agent, FormField, ResourceResponse } from '../../models'
import { handleResource } from '../DataTable/tableColumns/handleResource'
import { Modal } from '../Modal/Modal'
import { Form } from './Form'
import { agentFormFields } from './formFields'
import { agentFormFields, agentInitialValues } from './formFields'

export const AgentSelector: FC<{
formItem: FormField
Expand Down Expand Up @@ -69,6 +69,7 @@ export const AgentSelector: FC<{
<Modal isVisible={isModalVisible} title="Add new agent" onClose={() => setIsModalVisible(false)}>
<Form
formFields={agentFormFields}
initialValues={agentInitialValues}
onSubmit={async (formValues) => {
await handleResource({
resource: 'agents',
Expand Down
4 changes: 2 additions & 2 deletions src/components/Form/Form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { screen } from '@testing-library/react'
import React from 'react'
import { customRender } from '../../utils/testUtils/testUtils'
import { Form } from './Form'
import { agentFormFields } from './formFields'
import { agentFormFields, agentInitialValues } from './formFields'

describe('Form', () => {
it('should render the component', () => {
const onSubmit = jest.fn()

customRender(<Form formFields={agentFormFields} onSubmit={onSubmit} />)
customRender(<Form formFields={agentFormFields} initialValues={agentInitialValues} onSubmit={onSubmit} />)

expect(screen.getByText(agentFormFields.name.label)).toBeInTheDocument()
})
Expand Down
Loading

0 comments on commit 6a38840

Please sign in to comment.