Skip to content

Commit

Permalink
feat(config): add nanoid for scan modes, proxies and points and retri…
Browse files Browse the repository at this point in the history
…eve specific south/north from api call
  • Loading branch information
burgerni10 authored and Nicolas Burger committed Dec 14, 2022
1 parent 592e03a commit e995f28
Show file tree
Hide file tree
Showing 22 changed files with 418 additions and 9,954 deletions.
20 changes: 20 additions & 0 deletions src/engine/base-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ export default class BaseEngine {
}))
}

/**
* Retrieve a south connector from the config
* @param {String} id - The South ID
* @returns {Object} - The connector
*/
getSouth(id) {
const { southConfig } = this.configService.getConfig()
return southConfig.find((southConnector) => southConnector.id === id)
}

/**
* Return the North connector
* @param {Object} configuration - The North connector configuration
Expand Down Expand Up @@ -205,4 +215,14 @@ export default class BaseEngine {
category,
}))
}

/**
* Retrieve a north connector from the config
* @param {String} id - The North ID
* @returns {Object} - The connector
*/
getNorth(id) {
const { northConfig } = this.configService.getConfig()
return northConfig.find((northConnector) => northConnector.id === id)
}
}
2 changes: 2 additions & 0 deletions src/frontend/components/oib-form/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import OibTitle from './oib-title.jsx'
import OibAuthentication from './oib-authentication.jsx'
import OibTimezone from './oib-timezone.jsx'
import OibCron from './oib-cron.jsx'
import OibDate from './oib-date.jsx'

export {
OibCheckbox,
Expand All @@ -25,5 +26,6 @@ export {
OibTitle,
OibAuthentication,
OibTimezone,
OibDate,
OibCron,
}
2 changes: 1 addition & 1 deletion src/frontend/components/oib-form/oib-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const OibSelect = ({ label, help, valid, value, options, optionsLabel, name, onC
invalid={validCheck !== null}
>
{options.map((o, index) => (
<option key={optionsLabel[index] || o} value={o}>
<option key={o} value={o}>
{optionsLabel[index] || o}
</option>
))}
Expand Down
202 changes: 103 additions & 99 deletions src/frontend/components/points-component.jsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,42 @@
import React from 'react'
import React, { useEffect } from 'react'
import { Button, Input } from 'reactstrap'

import PropTypes from 'prop-types'
import objectPath from 'object-path'
import { nanoid } from 'nanoid'
import Table from './table/table.jsx'
import TablePagination from './table/table-pagination.jsx'
import Modal from './modal.jsx'
import SouthSchemas from '../south/south-types.jsx'
import * as Controls from './oib-form/index.js'
import utils from '../helpers/utils.js'
import validation from '../south/form/south.validation.js'

// Max points on one page
const MAX_ON_PAGE = 10
// This value will be used to calculate the amount of max pagination displayed
const MAX_PAGINATION_DISPLAY = 11

const PointsComponent = ({
southId,
points: pointsOrdered,
southType,
handleDeleteAllPoint,
handleAdd,
handleDelete,
handleImportPoints,
onUpdate,
prefix,
schema,
points,
onChange,
}) => {
const [filterText, setFilterText] = React.useState('') // used to limit the list of points
const [selectedPage, setSelectedPage] = React.useState(1)
// max points on one page
const MAX_ON_PAGE = 10
// this value will be used to calculate the amount of max pagination displayed
const MAX_PAGINATION_DISPLAY = 11
const pageOffset = selectedPage * MAX_ON_PAGE - MAX_ON_PAGE

// add virtualIndex for each point for helping the filter
const points = pointsOrdered.slice().map((item, index) => ({ virtualIndex: index, ...item })).reverse()

// filter
const filteredPoints = filterText
? points.filter(
(point) => {
// remove the virtualIndex from filterable attributes
const filterableAttributes = { ...point }
delete filterableAttributes.virtualIndex
return Object.values(filterableAttributes).findIndex((element) => element
.toString()
.toLowerCase()
.includes(filterText.toLowerCase())) >= 0
},
) : points
const [allPoints, setAllPoints] = React.useState(points)
const [filteredPoints, setFilteredPoints] = React.useState(points)
const [tableRows, setTableRows] = React.useState([])

/**
* @param {number} index the index of a point in the table
* @returns {number} the index in the config file of the chosen point
*/
const findIndexBasedOnVirtualIndex = (index) => {
const paginatedIndex = MAX_ON_PAGE * (selectedPage - 1) + index
const pointToOperate = filteredPoints[paginatedIndex]
return pointsOrdered.findIndex((point) => point.pointId === pointToOperate.pointId)
}
// configure help if exists
const pointsWithHelp = Object.entries(schema.points).filter(([name, value]) => name && value.help)
const tableHelps = pointsWithHelp.length > 0 && pointsWithHelp.map(([name, value]) => (
<div key={name}>
<b>{`${value.label || name}: `}</b>
{value.help}
</div>
))
// configure table header and rows
const tableHeaders = Object.entries(schema.points).map(([_name, value]) => value.label || _name)

/**
* Sets the filter text
Expand All @@ -66,37 +49,50 @@ const PointsComponent = ({
}

/**
* add point
* Add a point to the list
* @returns {void}
*/
const handleAddPoint = () => {
const newPoint = Object.entries(schema.points).map(([name]) => name).reduce((previousValue, currentValue) => {
objectPath.set(previousValue, currentValue, '')
return previousValue
}, {})
newPoint.id = nanoid()
const newAllPoints = [...allPoints]
newAllPoints.unshift(newPoint)
setAllPoints(newAllPoints)
setSelectedPage(1) // jump to first page, to see new row
handleAdd(Object.entries(SouthSchemas[southType].points).map(([name]) => name))
onChange(prefix, newAllPoints)
}

const handleChange = (idAndName, value) => {
const [id, name] = idAndName.split('.')
const newAllPoints = [...allPoints]
const pointToUpdate = newAllPoints.find((point) => point.id === id)
pointToUpdate[name] = value
setAllPoints(newAllPoints)
}

/**
* Delete point
* @param {number} index the index of point
* Delete point by its ID
* @param {string} id the ID of point
* @returns {void}
*/
const handleDeletePoint = (index) => {
const indexInConfig = findIndexBasedOnVirtualIndex(index)
handleDelete(indexInConfig)
}

const onChange = (name, value, validity) => {
// add pageOffset before dispatch the update to update the correct point (pagination)
const index = Number(name.match(/[0-9]+/g))
const pathWithPageOffset = name.replace(/[0-9]+/g, `${findIndexBasedOnVirtualIndex(index)}`)
onUpdate(pathWithPageOffset, value, validity)
const handleDeletePoint = (id) => {
const newAllPoints = allPoints.filter((point) => point.id !== id)
setAllPoints(newAllPoints)
onChange(prefix, newAllPoints)
}

/**
* Download export file of points
* @returns {void}
*/
const handleExportPoints = () => {
const csvString = utils.createCSV(pointsOrdered)
const csvString = utils.createCSV(allPoints.map((point) => {
delete point.id
return point
}))
const element = document.createElement('a')
const file = new Blob([csvString], { type: 'text/csv' })
element.href = URL.createObjectURL(file)
Expand All @@ -106,39 +102,51 @@ const PointsComponent = ({
document.body.removeChild(element)
}

const southSchema = SouthSchemas[southType]
// configure help if exists
const pointsWithHelp = Object.entries(southSchema.points).filter(([name, value]) => name && value.help)
const tableHelps = pointsWithHelp.length > 0 && pointsWithHelp.map(([name, value]) => (
<div key={name}>
<b>{`${value.label || name}: `}</b>
{value.help}
</div>
))
// configure table header and rows
const tableHeaders = Object.entries(southSchema.points).map(([_name, value]) => value.label || _name)

// paging
const pagedPoints = filteredPoints.filter((_, index) => index >= pageOffset && index < selectedPage * MAX_ON_PAGE)
const tableRows = pagedPoints.map((point, index) => Object.entries(southSchema.points).map(([key, value]) => {
const { type, ...rest } = value
const Control = Controls[type]
rest.value = point[key]
rest.label = null // remove field title in table rows
rest.help = null // remove help in table rows
// check if the key must be unique and extend already existing validation with isUnique check
if (southSchema.points[key].unique) {
const indexOffset = (selectedPage - 1) * MAX_ON_PAGE
const pointIds = points.filter((_point) => _point.virtualIndex !== filteredPoints[indexOffset + index].virtualIndex).map((p) => p[key])
const oldValid = rest.valid.bind({})
rest.valid = (val) => oldValid(val) || validation.points.isUnique(val, pointIds) || validation.points.noUnintendedTrailingSpaces(val)
/**
* Send the imported file content to the backend
* @param {Object} file the file returned by input
* @returns {void}
*/
const handleImportPoints = async (file) => {
try {
const text = await utils.readFileContent(file)
const newAllPoints = await utils.parseCSV(text)
setAllPoints(newAllPoints.map((point) => ({ ...point, id: nanoid() })))
onChange(prefix, newAllPoints)
} catch (error) {
console.error(error)
}
const name = `points.${index}.${key}`
return (
/* eslint-disable-next-line react/jsx-props-no-spreading */
{ name, value: <Control onChange={onChange} name={name} {...rest} southId={southId} /> }
)
}))
}

const handleDeleteAllPoints = () => {
setAllPoints([])
onChange(prefix, [])
}

useEffect(() => {
const pageOffset = selectedPage * MAX_ON_PAGE - MAX_ON_PAGE
const newFilteredPoints = filterText !== '' ? allPoints.filter((point) => Object.values(point).findIndex((element) => element
.toString()
.toLowerCase()
.includes(filterText.toLowerCase())) >= 0) : allPoints

const newTableRows = newFilteredPoints.filter((_, index) => index >= pageOffset && index < selectedPage * MAX_ON_PAGE)
.map((point) => Object.entries(schema.points).map(([key, value]) => {
const { type, ...rest } = value
const Control = Controls[type]
rest.value = point[key]
rest.label = null // remove field title in table rows
rest.help = null // remove help in table rows
return (
// id is used to remove the point from its id with handle delete
/* eslint-disable-next-line react/jsx-props-no-spreading */
{ id: point.id, name: `${point.id}.${key}`, value: <Control onChange={handleChange} name={`${point.id}.${key}`} {...rest} /> }
)
}))

setFilteredPoints(newFilteredPoints)
setTableRows(newTableRows)
}, [filterText, selectedPage, allPoints])

return (
<div className="m-2">
Expand All @@ -151,7 +159,7 @@ const PointsComponent = ({
onChange={(_name, val) => updateFilterText(val)}
/>
<Table help={tableHelps || []} headers={tableHeaders} rows={tableRows} handleAdd={handleAddPoint} handleDelete={handleDeletePoint} />
{filteredPoints.length && (
{filteredPoints.length > 0 && (
<TablePagination
maxToDisplay={MAX_PAGINATION_DISPLAY}
selected={selectedPage}
Expand All @@ -177,11 +185,11 @@ const PointsComponent = ({
<Modal
show={false}
title="Delete All Points"
body="Are you sure you want to delete All Points from this Data Source?"
body="Are you sure you want to delete all points from this connector?"
>
{(confirm) => (
<div>
<Button className="inline-button" color="danger" onClick={confirm(() => handleDeleteAllPoint())}>
<Button className="inline-button" color="danger" onClick={confirm(() => handleDeleteAllPoints())}>
Delete All Points
</Button>
</div>
Expand All @@ -193,14 +201,10 @@ const PointsComponent = ({
}

PointsComponent.propTypes = {
southId: PropTypes.string.isRequired,
points: PropTypes.arrayOf(PropTypes.object).isRequired,
southType: PropTypes.string.isRequired,
handleDeleteAllPoint: PropTypes.func.isRequired,
handleAdd: PropTypes.func.isRequired,
handleDelete: PropTypes.func.isRequired,
handleImportPoints: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
prefix: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
}

export default PointsComponent
2 changes: 1 addition & 1 deletion src/frontend/components/table/table-rows.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const TableRows = ({
<FaTrashAlt
className="oi-icon mx-2"
onClick={confirm(() => {
handleDelete(index)
handleDelete(row[0].id || index)
})}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/context/config-context.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { ConfigProvider, reducer } from './config-context.jsx'
global.fetch = jest.fn().mockImplementation((uri) => {
let jsonString
switch (uri) {
case '/config/schemas/north':
case '/api/installed-north':
jsonString = JSON.stringify(['a', 'b', 'c'])
break
case '/config/schemas/south':
case '/api/installed-south':
jsonString = JSON.stringify(['d', 'e', 'f'])
break
case '/config':
Expand Down
Loading

0 comments on commit e995f28

Please sign in to comment.