Merge branch 'dev' into fix/coinatmradar-fix-7.5

This commit is contained in:
csrapr 2020-12-14 17:38:05 +00:00 committed by GitHub
commit bb3c9ef2ec
9 changed files with 185 additions and 131 deletions

View file

@ -3,6 +3,7 @@ const { parseAsync } = require('json2csv')
const { GraphQLDateTime } = require('graphql-iso-date')
const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json')
const got = require('got')
const DataLoader = require('dataloader')
const machineLoader = require('../../machine-loader')
const customers = require('../../customers')
@ -265,6 +266,8 @@ const typeDefs = gql`
}
`
const transactionsLoader = new DataLoader(ids => transactions.getCustomerTransactionsBatch(ids))
const notify = () => got.post('http://localhost:3030/dbChange')
.catch(e => console.error('Error: lamassu-server not responding'))
@ -273,7 +276,7 @@ const resolvers = {
JSONObject: GraphQLJSONObject,
Date: GraphQLDateTime,
Customer: {
transactions: parent => transactions.getCustomerTransactions(parent.id)
transactions: parent => transactionsLoader.load(parent.id)
},
Query: {
countries: () => countries,

View file

@ -1,4 +1,5 @@
const _ = require('lodash/fp')
const pgp = require('pg-promise')()
const db = require('../db')
const machineLoader = require('../machine-loader')
@ -65,9 +66,8 @@ function batch (from = new Date(0).toISOString(), until = new Date().toISOString
.then(packager)
}
function getCustomerTransactions (customerId) {
function getCustomerTransactionsBatch (ids) {
const packager = _.flow(it => {
console.log()
return it
}, _.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
@ -82,7 +82,7 @@ function getCustomerTransactions (customerId) {
((not txs.send_confirmed) and (txs.created <= now() - interval $2)) as expired
from cash_in_txs as txs
left outer join customers c on txs.customer_id = c.id
where c.id = $1
where c.id IN ($1^)
order by created desc limit $3`
const cashOutSql = `select 'cashOut' as tx_class,
@ -100,14 +100,16 @@ function getCustomerTransactions (customerId) {
inner join cash_out_actions actions on txs.id = actions.tx_id
and actions.action = 'provisionAddress'
left outer join customers c on txs.customer_id = c.id
where c.id = $1
where c.id IN ($1^)
order by created desc limit $2`
return Promise.all([
db.any(cashInSql, [customerId, cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
db.any(cashOutSql, [customerId, NUM_RESULTS, REDEEMABLE_AGE])
db.any(cashInSql, [_.map(pgp.as.text, ids).join(','), cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
db.any(cashOutSql, [_.map(pgp.as.text, ids).join(','), NUM_RESULTS, REDEEMABLE_AGE])
])
.then(packager)
.then(packager).then(transactions => {
const transactionMap = _.groupBy('customerId', transactions)
return ids.map(id => transactionMap[id])
})
}
function single (txId) {
@ -156,4 +158,4 @@ function cancel (txId) {
.then(() => single(txId))
}
module.exports = { batch, getCustomerTransactions, single, cancel }
module.exports = { batch, single, cancel, getCustomerTransactionsBatch }

View file

@ -1,4 +1,5 @@
import CssBaseline from '@material-ui/core/CssBaseline'
import Grid from '@material-ui/core/Grid'
import {
StylesProvider,
jssPreset,
@ -8,12 +9,18 @@ import {
import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
import React, { createContext, useContext, useState } from 'react'
import { useLocation, BrowserRouter as Router } from 'react-router-dom'
import {
useLocation,
useHistory,
BrowserRouter as Router
} from 'react-router-dom'
import Sidebar from 'src/components/layout/Sidebar'
import TitleSection from 'src/components/layout/TitleSection'
import ApolloProvider from 'src/utils/apollo'
import Header from './components/layout/Header'
import { tree, Routes } from './routing/routes'
import { tree, hasSidebar, Routes, getParent } from './routing/routes'
import global from './styling/global'
import theme from './styling/theme'
import { backgroundColor, mainWidth } from './styling/variables'
@ -46,6 +53,18 @@ const useStyles = makeStyles({
flex: 1,
display: 'flex',
flexDirection
},
grid: {
flex: 1,
height: '100%'
},
contentWithSidebar: {
flex: 1,
marginLeft: 48,
paddingTop: 15
},
contentWithoutSidebar: {
width: mainWidth
}
})
@ -54,15 +73,45 @@ const AppContext = createContext()
const Main = () => {
const classes = useStyles()
const location = useLocation()
const history = useHistory()
const { wizardTested } = useContext(AppContext)
const route = location.pathname
const sidebar = hasSidebar(route)
const parent = sidebar ? getParent(route) : {}
const is404 = location.pathname === '/404'
const isSelected = it => location.pathname === it.route
const onClick = it => history.push(it.route)
const contentClassName = sidebar
? classes.contentWithSidebar
: classes.contentWithoutSidebar
return (
<div className={classes.root}>
{!is404 && wizardTested && <Header tree={tree} />}
<main className={classes.wrapper}>
<Routes />
{sidebar && !is404 && wizardTested && (
<TitleSection title={parent.title}></TitleSection>
)}
<Grid container className={classes.grid}>
{sidebar && !is404 && wizardTested && (
<Sidebar
data={parent.children}
isSelected={isSelected}
displayName={it => it.label}
onClick={onClick}
/>
)}
<div className={contentClassName}>
<Routes />
</div>
</Grid>
</main>
</div>
)

View file

@ -3,9 +3,11 @@ import classnames from 'classnames'
import React, { memo, useState } from 'react'
import { NavLink, useHistory } from 'react-router-dom'
import { Link } from 'src/components/buttons'
import ActionButton from 'src/components/buttons/ActionButton'
import { H4 } from 'src/components/typography'
import AddMachine from 'src/pages/AddMachine'
import { ReactComponent as AddIconReverse } from 'src/styling/icons/button/add/white.svg'
import { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg'
import { ReactComponent as Logo } from 'src/styling/icons/menu/logo.svg'
import styles from './Header.styles'
@ -76,9 +78,13 @@ const Header = memo(({ tree }) => {
</NavLink>
))}
</ul>
<Link color="action" onClick={() => setOpen(true)}>
Add Machine
</Link>
<ActionButton
color="secondary"
Icon={AddIcon}
InverseIcon={AddIconReverse}
onClick={() => setOpen(true)}>
Add machine
</ActionButton>
</nav>
</div>
</div>

View file

@ -128,19 +128,19 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
/>
<ConfirmDialog
open={confirmDialogOpen}
title={`${action?.command} this machine?`}
title={`${action?.display} this machine?`}
errorMessage={errorMessage}
toBeConfirmed={machine.name}
message={action?.message}
confirmationMessage={action?.confirmationMessage}
saveButtonAlwaysEnabled={action?.command === 'Rename'}
saveButtonAlwaysEnabled={action?.command === 'rename'}
onConfirmed={value => {
setErrorMessage(null)
machineAction({
variables: {
deviceId: machine.deviceId,
action: `${action?.command}`.toLowerCase(),
...(action?.command === 'Rename' && { newName: value })
action: `${action?.command}`,
...(action?.command === 'rename' && { newName: value })
}
})
}}
@ -174,7 +174,8 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
InverseIcon={EditReversedIcon}
onClick={() =>
setAction({
command: 'Rename',
command: 'rename',
display: 'Rename',
confirmationMessage: 'Write the new name for this machine'
})
}>
@ -188,7 +189,8 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
disabled={loading}
onClick={() =>
setAction({
command: 'Unpair'
command: 'unpair',
display: 'Unpair'
})
}>
Unpair
@ -201,26 +203,42 @@ const MachineDetailsRow = ({ it: machine, onActionSuccess }) => {
disabled={loading}
onClick={() =>
setAction({
command: 'Reboot'
command: 'reboot',
display: 'Reboot'
})
}>
Reboot
</ActionButton>
<ActionButton
className={classes.inlineChip}
className={classes.mr}
disabled={loading}
color="primary"
Icon={ShutdownIcon}
InverseIcon={ShutdownReversedIcon}
onClick={() =>
setAction({
command: 'Shutdown',
command: 'shutdown',
display: 'Shutdown',
message:
'In order to bring it back online, the machine will need to be visited and its power reset.'
})
}>
Shutdown
</ActionButton>
<ActionButton
color="primary"
className={classes.inlineChip}
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
disabled={loading}
onClick={() =>
setAction({
command: 'restartServices',
display: 'Restart services for'
})
}>
Restart Services
</ActionButton>
</div>
</Item>
</Container>

View file

@ -1,100 +0,0 @@
import { makeStyles } from '@material-ui/core'
import Grid from '@material-ui/core/Grid'
import React from 'react'
import {
Route,
Switch,
Redirect,
useLocation,
useHistory
} from 'react-router-dom'
import Sidebar from 'src/components/layout/Sidebar'
import TitleSection from 'src/components/layout/TitleSection'
import CoinAtmRadar from './CoinATMRadar'
import ContactInfo from './ContactInfo'
import ReceiptPrinting from './ReceiptPrinting'
import TermsConditions from './TermsConditions'
const styles = {
grid: {
flex: 1,
height: '100%'
},
content: {
flex: 1,
marginLeft: 48,
paddingTop: 15
}
}
const useStyles = makeStyles(styles)
const innerRoutes = [
{
label: 'Contact information',
route: '/settings/operator-info/contact-info',
component: ContactInfo
},
{
label: 'Receipt',
route: '/settings/operator-info/receipt-printing',
component: ReceiptPrinting
},
{
label: 'Coin ATM Radar',
route: '/settings/operator-info/coin-atm-radar',
component: CoinAtmRadar
},
{
label: 'Terms & Conditions',
route: '/settings/operator-info/terms-conditions',
component: TermsConditions
}
]
const Routes = ({ wizard }) => (
<Switch>
<Redirect
exact
from="/settings/operator-info"
to="/settings/operator-info/contact-info"
/>
<Route exact path="/" />
{innerRoutes.map(({ route, component: Page, key }) => (
<Route path={route} key={key}>
<Page name={key} wizard={wizard} />
</Route>
))}
</Switch>
)
const OperatorInfo = ({ wizard = false }) => {
const classes = useStyles()
const history = useHistory()
const location = useLocation()
const isSelected = it => location.pathname === it.route
const onClick = it => history.push(it.route)
return (
<>
<TitleSection title="Operator information"></TitleSection>
<Grid container className={classes.grid}>
<Sidebar
data={innerRoutes}
isSelected={isSelected}
displayName={it => it.label}
onClick={onClick}
/>
<div className={classes.content}>
<Routes wizard={wizard} />
</div>
</Grid>
</>
)
}
export default OperatorInfo

View file

@ -20,7 +20,10 @@ import MachineLogs from 'src/pages/MachineLogs'
import CashCassettes from 'src/pages/Maintenance/CashCassettes'
import MachineStatus from 'src/pages/Maintenance/MachineStatus'
import Notifications from 'src/pages/Notifications/Notifications'
import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo'
import CoinAtmRadar from 'src/pages/OperatorInfo/CoinATMRadar'
import ContactInfo from 'src/pages/OperatorInfo/ContactInfo'
import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting'
import TermsConditions from 'src/pages/OperatorInfo/TermsConditions'
import ServerLogs from 'src/pages/ServerLogs'
import Services from 'src/pages/Services/Services'
import TokenManagement from 'src/pages/TokenManagement/TokenManagement'
@ -125,7 +128,36 @@ const tree = [
key: namespaces.OPERATOR_INFO,
label: 'Operator Info',
route: '/settings/operator-info',
component: OperatorInfo
title: 'Operator Information',
get component() {
return () => <Redirect to={this.children[0].route} />
},
children: [
{
key: 'contact-info',
label: 'Contact information',
route: '/settings/operator-info/contact-info',
component: ContactInfo
},
{
key: 'receipt-printing',
label: 'Receipt',
route: '/settings/operator-info/receipt-printing',
component: ReceiptPrinting
},
{
key: 'coin-atm-radar',
label: 'Coin ATM Radar',
route: '/settings/operator-info/coin-atm-radar',
component: CoinAtmRadar
},
{
key: 'terms-conditions',
label: 'Terms & Conditions',
route: '/settings/operator-info/terms-conditions',
component: TermsConditions
}
]
}
]
},
@ -181,10 +213,34 @@ const tree = [
]
const map = R.map(R.when(R.has('children'), R.prop('children')))
const leafRoutes = R.compose(R.flatten, map)(tree)
const parentRoutes = R.filter(R.has('children'))(tree)
const mappedRoutes = R.compose(R.flatten, map)(tree)
const parentRoutes = R.filter(R.has('children'))(mappedRoutes).concat(
R.filter(R.has('children'))(tree)
)
const leafRoutes = R.compose(R.flatten, map)(mappedRoutes)
const flattened = R.concat(leafRoutes, parentRoutes)
const hasSidebar = route =>
R.any(r => r.route === route)(
R.compose(
R.flatten,
R.map(R.prop('children')),
R.filter(R.has('children'))
)(mappedRoutes)
)
const getParent = route =>
R.find(
R.propEq(
'route',
R.dropLast(
1,
R.dropLastWhile(x => x !== '/', route)
)
)
)(flattened)
const Routes = () => {
const history = useHistory()
const location = useLocation()
@ -215,4 +271,4 @@ const Routes = () => {
</Switch>
)
}
export { tree, Routes }
export { tree, getParent, hasSidebar, Routes }

19
package-lock.json generated
View file

@ -6131,6 +6131,25 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dasherize": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz",
"integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg="
},
"data-uri-to-buffer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
"integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
},
"dataloader": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.0.0.tgz",
"integrity": "sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ=="
},
"date-fns": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==",
"dev": true
},
"expand-brackets": {

View file

@ -23,6 +23,7 @@
"console-log-level": "^1.4.0",
"cookie-parser": "^1.4.3",
"cors": "^2.8.5",
"dataloader": "^2.0.0",
"ethereumjs-tx": "^1.3.3",
"ethereumjs-util": "^5.2.0",
"ethereumjs-wallet": "^0.6.3",