Skip to content

Commit

Permalink
Merge pull request #245 from ConsumerDataStandardsAustralia/feature/a…
Browse files Browse the repository at this point in the history
…emo-status-and-outages

AEMO Status and Outages
  • Loading branch information
sumayahasancds authored Dec 13, 2023
2 parents 310f982 + 3024265 commit ead18d7
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 66 deletions.
5 changes: 5 additions & 0 deletions src/components/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AppBar from '@material-ui/core/AppBar'
import Tabs from '@material-ui/core/Tabs'
import Tab from '@material-ui/core/Tab'
import DiscoveryInfo from './data/discovery/DiscoveryInfo'
import AEMODiscoveryInfo from './data/discovery/AEMODiscoveryInfo'

const useStyles = makeStyles(theme => ({
hidden: {
Expand All @@ -37,6 +38,7 @@ function Page() {
<Tab label="Banking" />
<Tab label="Energy" />
<Tab label="Status and Outages" />
<Tab label="AEMO - Status and Outages" />
</Tabs>
</AppBar>
<div className={value === 0 ? '' : classes.hidden}>
Expand All @@ -50,6 +52,9 @@ function Page() {
<div className={value === 2 ? '' : classes.hidden}>
<DiscoveryInfo/>
</div>
<div className={value === 3 ? '' : classes.hidden}>
<AEMODiscoveryInfo/>
</div>
</Container>
);
}
Expand Down
100 changes: 100 additions & 0 deletions src/components/data/discovery/AEMODiscoveryInfo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react'
import Accordion from '@material-ui/core/Accordion'
import AccordionSummary from '@material-ui/core/AccordionSummary'
import AccordionActions from '@material-ui/core/AccordionActions'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import SubjectIcon from '@material-ui/icons/Subject'
import Typography from '@material-ui/core/Typography'
import Divider from '@material-ui/core/Divider'
import RefreshIcon from '@material-ui/icons/Refresh'
import Fab from '@material-ui/core/Fab'
import Tooltip from '@material-ui/core/Tooltip'
import { makeStyles } from '@material-ui/core/styles'
import { fade } from '@material-ui/core/styles/colorManipulator'
import StatusOutages from './StatusOutages'
import { connect } from 'react-redux'
import { retrieveStatus, retrieveOutages } from '../../../store/aemo_discovery'

const useStyles = makeStyles(theme => ({
container: {
marginLeft: theme.typography.pxToRem(20),
marginRight: theme.typography.pxToRem(20)
},
panel: {
backgroundColor: fade('#fff', 0.9)
},
heading: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: theme.typography.pxToRem(20),
},
details: {
maxWidth:'95%',
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 20
}
}))

const AEMODiscoveryInfo = (props) => {

const classes = useStyles()
const [expanded, setExpanded] = React.useState(true)
const {statusDetails, outagesDetails} = props.data

const toggleExpansion = (event, newExpanded) => {
setExpanded(newExpanded)
}

const refreshStatusOutages = () => {
props.retrieveStatus()
props.retrieveOutages()
}

React.useEffect(() => {
refreshStatusOutages()
// eslint-disable-next-line
}, [])

return (
<Accordion defaultExpanded className={classes.panel} expanded={expanded} onChange={toggleExpansion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon/>}
aria-controls='panel1c-content'
>
<div className={classes.heading}>
<SubjectIcon/><Typography style={{paddingLeft: 8}}>Status &amp; Outages</Typography>
</div>
</AccordionSummary>
<div className={classes.details}>
<p>Secondary Data Holders provide data to Data Holders via CDR requests, who in turn provide the data to ADRs. Such data is called Shared Responsibility Data (SR data).</p>
<p>Currently, only the energy sector has a designated secondary data holder: AEMO. This page lists the status and outages for AEMO.</p>

<div className="title"><span><img src="https://www.aemo.com.au/-/media/project/aemo/global/logos/aemo-logo.svg" alt="AEMO"/></span></div>
<StatusOutages statusDetails={statusDetails} outagesDetails={outagesDetails} />
</div>

<Divider/>

<AccordionActions>
<Tooltip title='Refresh'>
<Fab size='medium' color='primary' onClick={refreshStatusOutages}>
<RefreshIcon/>
</Fab>
</Tooltip>
</AccordionActions>
</Accordion>
)
}

const mapStateToProps = state => ({
data: state.aemoDiscovery
})

const mapDispatchToProps = {
retrieveStatus,
retrieveOutages
}

export default connect(mapStateToProps, mapDispatchToProps)(AEMODiscoveryInfo)
24 changes: 18 additions & 6 deletions src/components/data/discovery/DiscoveryInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const DiscoveryInfo = (props) => {
setExpanded(newExpanded)
}

React.useEffect(() => {
refreshStatusOutages()
// eslint-disable-next-line
}, [props.dataSources])

const getWidth = (dataSourceCount, min) => {
return Math.max(12 / dataSourceCount, min)
}
Expand Down Expand Up @@ -78,8 +83,13 @@ const DiscoveryInfo = (props) => {
{
savedDataSourcesCount > 0 &&
<Grid container alignItems='flex-start' spacing={2} className={classes.container}>
{dataSources.map((dataSource, index) => (
!dataSource.unsaved && dataSource.enabled && !dataSource.deleted &&
{dataSources.map((dataSource, index) => {
const data = props.data[index]
if (!data) {
return false
}
const {statusDetails, outagesDetails} = data
return (!dataSource.unsaved && dataSource.enabled && !dataSource.deleted &&
<Grid item key={index}
xs={getWidth(savedDataSourcesCount, 12)}
sm={getWidth(savedDataSourcesCount, 12)}
Expand All @@ -88,9 +98,10 @@ const DiscoveryInfo = (props) => {
xl={getWidth(savedDataSourcesCount, 3)}
>
<div className="title">{!!dataSource.icon && <span><img src={dataSource.icon} alt=""/></span>}<h2>{dataSource.name}</h2></div>
<StatusOutages dataSource={dataSource} dataSourceIndex={index}/>
<StatusOutages statusDetails={statusDetails} outagesDetails={outagesDetails} />
</Grid>
))}
)
})}
</Grid>
}
</div>
Expand All @@ -108,10 +119,11 @@ const DiscoveryInfo = (props) => {
)
}

const mapStateToProps = state=>({
const mapStateToProps = state => ({
dataSources : state.dataSources,
savedDataSourcesCount: state.dataSources.filter(dataSource => !dataSource.unsaved && !dataSource.deleted && dataSource.enabled).length,
versionInfo: state.versionInfo.vHeaders
versionInfo: state.versionInfo.vHeaders,
data: state.discovery
})

const mapDispatchToProps = {
Expand Down
19 changes: 1 addition & 18 deletions src/components/data/discovery/StatusOutages.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,12 @@ import React from 'react'
import DateTime from '../DateTime'
import Duration from '../Duration'
import { connect } from 'react-redux'
import { normalise } from '../../../utils/url'
import { retrieveStatus, retrieveOutages } from '../../../store/discovery'
import { translateDiscoveryStatus } from '../../../utils/dict'

class StatusOutages extends React.Component {

componentDidMount() {
const { dataSourceIndex, dataSource, versionInfo } = this.props
const url = normalise(dataSource.url)
this.props.retrieveStatus(dataSourceIndex, url, versionInfo.xV, versionInfo.xMinV)
this.props.retrieveOutages(dataSourceIndex, url, versionInfo.xV, versionInfo.xMinV)
}

render() {
let data = this.props.data[this.props.dataSourceIndex]
if (!data) {
return false
}
const {statusDetails, outagesDetails} = data
const { statusDetails, outagesDetails } = this.props
return (
<>
{!!statusDetails &&
Expand Down Expand Up @@ -63,13 +50,9 @@ const Outage = props => {
}

const mapStateToProps = state => ({
versionInfo: state.versionInfo.vHeaders,
data: state.discovery
})

const mapDispatchToProps = {
retrieveStatus,
retrieveOutages
}

export default connect(mapStateToProps, mapDispatchToProps)(StatusOutages)
60 changes: 60 additions & 0 deletions src/store/aemo_discovery/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {conoutInfo} from '../conout/actions'
import {createConoutError, checkExposedHeaders} from '../../utils/cors'

const AEMO_URL = 'https://api.aemo.com.au/NEMRetail/cds-au/v1'

export const RETRIEVE_AEMO_STATUS = 'RETRIEVE_AEMO_STATUS'
export const RETRIEVE_AEMO_OUTAGES = 'RETRIEVE_AEMO_OUTAGES'

const headers = {
'Accept': 'application/json',
'x-v': 1
}

export const retrieveStatus = () => dispatch => {
const fullUrl = AEMO_URL + '/discovery/status'
const request = new Request(fullUrl, {headers})
dispatch(conoutInfo('Requesting retrieveStatus(): ' + fullUrl))
dispatch({
type: RETRIEVE_AEMO_STATUS,
payload: fetch(request)
.then(response => {
if (response.ok) {
checkExposedHeaders(response, fullUrl, dispatch)
return response.json()
}
throw new Error(`Response not OK. Status: ${response.status} (${response.statusText})`)
})
.then(obj => {
dispatch(conoutInfo(`Received response for ${fullUrl}:`, obj))
return obj
})
.catch(error => {
dispatch(createConoutError(error, fullUrl))
})
})
}

export const retrieveOutages = () => dispatch => {
const fullUrl = AEMO_URL + '/discovery/outages'
const request = new Request(fullUrl, {headers})
dispatch(conoutInfo('Requesting retrieveOutages(): ' + fullUrl))
dispatch({
type: RETRIEVE_AEMO_OUTAGES,
payload: fetch(request)
.then(response => {
if (response.ok) {
checkExposedHeaders(response, fullUrl, dispatch)
return response.json()
}
throw new Error(`Response not OK. Status: ${response.status} (${response.statusText})`)
})
.then(obj => {
dispatch(conoutInfo(`Received response for ${fullUrl}:`, obj))
return obj
})
.catch(error => {
dispatch(createConoutError(error, fullUrl))
})
})
}
2 changes: 2 additions & 0 deletions src/store/aemo_discovery/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './actions'
export * from './reducer'
28 changes: 28 additions & 0 deletions src/store/aemo_discovery/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
RETRIEVE_AEMO_STATUS,
RETRIEVE_AEMO_OUTAGES
} from './actions'
import {fulfilled} from '../../utils/async-actions'

export default function discovery(state = {}, action) {
switch (action.type) {
case fulfilled(RETRIEVE_AEMO_STATUS): {
const s = {...state}
if (action.payload) {
const response = action.payload
s.statusDetails = response ? response.data : null
}
return s
}
case fulfilled(RETRIEVE_AEMO_OUTAGES): {
const s = {...state}
if (action.payload) {
const response = action.payload
s.outagesDetails = response ? response.data : null
}
return s
}
default:
return state
}
}
17 changes: 3 additions & 14 deletions src/store/banking/data/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {conoutInfo, conoutError, conoutHtmlError, conoutWarn} from '../../conout/actions'
import {conoutInfo, conoutError, conoutWarn} from '../../conout/actions'
import {createConoutError, checkExposedHeaders} from '../../../utils/cors'
import {encodeRFC3986URIComponent} from '../../../utils/url'

export const START_RETRIEVE_PRODUCT_LIST = 'START_RETRIEVE_PRODUCT_LIST'
Expand All @@ -17,11 +18,6 @@ const headers = {
'Accept': 'application/json'
}

function createConoutError(error, url) {
return conoutError('Caught ' + error + ' while requesting ' + url + (error.name === 'TypeError' ?
' Possibly caused by the endpoint not supporting Cross-Origin Requests (CORS)' : ''))
}

export const retrieveProductList = (dataSourceIdx, baseUrl, productListUrl, xV, xMinV) =>
(dispatch) => {
const request = new Request(productListUrl, {headers: new Headers({...headers, 'x-v': xV, 'x-min-v': xMinV})})
Expand All @@ -31,14 +27,7 @@ export const retrieveProductList = (dataSourceIdx, baseUrl, productListUrl, xV,
payload: fetch(request)
.then(response => {
if (response.ok) {
if (!response.headers['x-v']) {
const msg = `Response for ${productListUrl}: doesn't expose header x-v: possibly caused by incomplete `
const corsSupport = 'CORS support'
dispatch(conoutHtmlError(
msg + corsSupport,
`${msg}<a href="https://cdr-support.zendesk.com/hc/en-us/articles/900003054706-CORS-support" target="_blank">${corsSupport}</a>`
))
}
checkExposedHeaders(response, productListUrl, dispatch)
return response.json()
}
throw new Error(`Response not OK. Status: ${response.status} (${response.statusText})`)
Expand Down
18 changes: 4 additions & 14 deletions src/store/discovery/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {conoutInfo, conoutHtmlError, conoutError} from '../conout/actions'
import {conoutInfo} from '../conout/actions'
import {createConoutError, checkExposedHeaders} from '../../utils/cors'

export const RETRIEVE_STATUS = 'RETRIEVE_STATUS'
export const RETRIEVE_OUTAGES = 'RETRIEVE_OUTAGES'
Expand All @@ -7,11 +8,6 @@ const headers = {
'Accept': 'application/json'
}

function createConoutError(error, url) {
return conoutError('Caught ' + error + ' while requesting ' + url + (error.name === 'TypeError' ?
' Possibly caused by the endpoint not supporting Cross-Origin Requests (CORS)' : ''))
}

export const retrieveStatus = (dataSourceIdx, url, xV, xMinV) => dispatch => {
const fullUrl = url + '/discovery/status'
const request = new Request(fullUrl, {
Expand All @@ -23,6 +19,7 @@ export const retrieveStatus = (dataSourceIdx, url, xV, xMinV) => dispatch => {
payload: fetch(request)
.then(response => {
if (response.ok) {
checkExposedHeaders(response, fullUrl, dispatch)
return response.json()
}
throw new Error(`Response not OK. Status: ${response.status} (${response.statusText})`)
Expand All @@ -48,14 +45,7 @@ export const retrieveOutages = (dataSourceIdx, url, xV, xMinV) => dispatch => {
payload: fetch(request)
.then(response => {
if (response.ok) {
if (!response.headers['x-v']) {
const msg = `Response for ${fullUrl}: doesn't expose header x-v: possibly caused by incomplete `
const corsSupport = 'CORS support'
dispatch(conoutHtmlError(
msg + corsSupport,
`${msg}<a href="https://cdr-support.zendesk.com/hc/en-us/articles/900003054706-CORS-support" target="_blank">${corsSupport}</a>`
))
}
checkExposedHeaders(response, fullUrl, dispatch)
return response.json()
}
throw new Error(`Response not OK. Status: ${response.status} (${response.statusText})`)
Expand Down
Loading

0 comments on commit ead18d7

Please sign in to comment.