diff --git a/.env.default b/.env.default index a31cc652a291d2fc8b70182ec96e9ba96c0fe8ec..493a9b48c4c579d488bdbd189fb327bd7324d480 100644 --- a/.env.default +++ b/.env.default @@ -26,5 +26,6 @@ REACT_APP_AUTH0_CALLBACK_URL REACT_APP_AUTH0_CLAIMS_NAMESPACE REACT_APP_AUTH0_AUDIENCE -# BlocPower Building Group ID +# Building Group IDs for Reports REACT_APP_BP_BGROUP_ID +REACT_APP_CBRA_BGROUP_ID diff --git a/README.md b/README.md index 4cba54a1e6e52985f7202cb655547ef101ea107f..15ad3e5f13ff4f301b38a90bf59989dbcf240199 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ REACT_APP_AUTH0_CLAIMS_NAMESPACE REACT_APP_AUTH0_AUDIENCE REACT_APP_FEEDBACK_LINK REACT_APP_BP_BGROUP_ID +REACT_APP_CBRA_BGROUP_ID ``` ### Optional - [React Devtools](https://github.com/facebook/react-devtools) diff --git a/src/containers/BGroup/BGroup.js b/src/containers/BGroup/BGroup.js index 458b4f417d21a70960f130150895c5952d56e499..dffc38bd1fbda9f4d8f5742c95e0adc84b6d2913 100644 --- a/src/containers/BGroup/BGroup.js +++ b/src/containers/BGroup/BGroup.js @@ -22,7 +22,7 @@ import BGroupProjectOverview from './BGroupProjectOverview'; import BGroupBuildingTable from './BGroupBuildingTable'; import completeProjectPropTypes from '../Project/propTypes'; import { loadProjects } from '../Project/actions'; -import { loadCustomBpImpactReport } from '../Reports/actions'; +import { loadCustomImpactReport } from '../Reports/actions'; import reportsPropTypes from '../Reports/propTypes'; import userPropType from '../User/propTypes'; import request from '../../utils/request'; @@ -85,7 +85,9 @@ export class BGroup extends Component { } this.getSimulationDates(buildingIds); this.getSfBuildingImpact(buildingIds); - this.props.loadCustomBpImpactReport(buildingIds); + if (nextProps.user.permissions['read::CustomImpact']) { + this.props.loadCustomImpactReport(buildingIds); + } } } if (nextProps.projects.projects !== this.props.projects.project) { @@ -326,19 +328,20 @@ export class BGroup extends Component { projects={this.props.projects} projectStatusBreakdown={this.state.projectStatusBreakdown} projectTypeBreakdown={this.state.projectTypeBreakdown} - impactSummary={this.props.report.customBpImpactReport} - impactSummaryLoading={this.props.report.loadingCustomBpImpact} + impactSummary={this.props.report.customImpactReport} + impactSummaryLoading={this.props.report.loadingCustomImpact} /> ); break; case 'buildings': content = ( View all groups {' '} -

- {this.props.bGroup.bGroupDetail.name} -

+ {this.props.bGroup.bGroupDetail.name ? ( +

+ {this.props.bGroup.bGroupDetail.name} +

+ ) : null + } {this.renderEditButton()} @@ -439,7 +445,7 @@ BGroup.propTypes = { user: userPropType, projects: completeProjectPropTypes, report: reportsPropTypes, - loadCustomBpImpactReport: PropTypes.func, + loadCustomImpactReport: PropTypes.func, loadProjects: PropTypes.func, displayNavBar: PropTypes.bool, displayProjectOverview: PropTypes.bool, @@ -486,7 +492,7 @@ const mapDispatchToProps = dispatch => ( deleteBuildingFromBGroup, deleteBGroup, loadProjects, - loadCustomBpImpactReport, + loadCustomImpactReport, }, dispatch) ); diff --git a/src/containers/BGroup/BGroupBuildingTable.js b/src/containers/BGroup/BGroupBuildingTable.js index ab26d832f8e74733c3ce2054e5ec2ec0a89bcacf..41b32d3e66ed6f9476ddfa476a1a4b6fb3d0d6a3 100644 --- a/src/containers/BGroup/BGroupBuildingTable.js +++ b/src/containers/BGroup/BGroupBuildingTable.js @@ -334,7 +334,7 @@ export default class BGroupBuildingTable extends Component { ) : null), }, ], - }], ...(!this.state.displayImpact) ? [] : [{ + }], ...(!this.props.user.permissions['read::SfBuildingImpact'] || !this.state.displayImpact) ? [] : [{ Header: () => ( Impact{' '} @@ -499,7 +499,7 @@ export default class BGroupBuildingTable extends Component { }, ], }]]; - if (this.state.edit) { + if (this.props.edit) { columns.push({ Header: '', filterable: false, @@ -559,7 +559,7 @@ export default class BGroupBuildingTable extends Component { }); return (
-
+
{ this.setState({ displayImpact: !this.state.displayImpact }); }} @@ -731,6 +732,7 @@ export default class BGroupBuildingTable extends Component { } BGroupBuildingTable.propTypes = { + edit: PropTypes.bool, bGroupId: PropTypes.string, user: userPropTypes, projects: completeProjectPropTypes, diff --git a/src/containers/BGroup/BGroupProjectOverview.js b/src/containers/BGroup/BGroupProjectOverview.js index cb9e4933f7bfa28a64d15d7a9aa322ceed3601d1..5c853d8cfb3581b10ef028da899da6f3b68d9d62 100644 --- a/src/containers/BGroup/BGroupProjectOverview.js +++ b/src/containers/BGroup/BGroupProjectOverview.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import userPropTypes from '../User/propTypes'; import completeProjectPropTypes from '../Project/propTypes'; import Loading from '../../components/Loading'; -import BpImpact from '../../containers/Reports/components/BpImpact/BpImpact'; +import Impact from '../../containers/Reports/components/Impact/Impact'; export default class BGroupProjectOverview extends Component { state = { }; @@ -29,11 +29,11 @@ export default class BGroupProjectOverview extends Component { const stageOrder = ['Engaged', 'Out to Bid', 'In Construction', 'Complete', 'HPD Pipeline']; return (
-
+

Project Impact Summary

-
diff --git a/src/containers/Reports/actions.js b/src/containers/Reports/actions.js index 8941c6052ec5a7f5d7fb8f40264bf99b5ac41757..9867bbc51178453a171e85eaadf18b5e69a4f5cc 100644 --- a/src/containers/Reports/actions.js +++ b/src/containers/Reports/actions.js @@ -6,9 +6,12 @@ import { LOAD_BP_IMPACT_REPORT, LOAD_BP_IMPACT_REPORT_SUCCESS, LOAD_BP_IMPACT_REPORT_ERROR, - LOAD_CUSTOM_BP_IMPACT_REPORT, - LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS, - LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR, + LOAD_CBRA_IMPACT_REPORT, + LOAD_CBRA_IMPACT_REPORT_SUCCESS, + LOAD_CBRA_IMPACT_REPORT_ERROR, + LOAD_CUSTOM_IMPACT_REPORT, + LOAD_CUSTOM_IMPACT_REPORT_SUCCESS, + LOAD_CUSTOM_IMPACT_REPORT_ERROR, } from './constants'; /** @@ -67,28 +70,55 @@ export function bpImpactReportsLoadingError(error) { } /** - * Load custom bp impact reports + * Load impact reports * - * @returns {object} An action object with a type of LOAD_CUSTOM_BP_IMPACT_REPORT + * @returns {object} An action object with a type of LOAD_BUILDING_CBRA_IMPACT_REPORT */ -export function loadCustomBpImpactReport(buildingIds) { +export function loadCbraImpactReports(limit = -1) { return { - type: LOAD_CUSTOM_BP_IMPACT_REPORT, + type: LOAD_CBRA_IMPACT_REPORT, + limit, + }; +} + +export function cbraImpactReportsLoaded(cbraImpactReports) { + return { + type: LOAD_CBRA_IMPACT_REPORT_SUCCESS, + payload: cbraImpactReports.data, + }; +} + +export function cbraImpactReportsLoadingError(error) { + return { + type: LOAD_CBRA_IMPACT_REPORT_ERROR, + error, + }; +} + +/** + * Load custom impact reports + * + * @returns {object} An action object with a type of LOAD_CUSTOM_IMPACT_REPORT + */ + +export function loadCustomImpactReport(buildingIds) { + return { + type: LOAD_CUSTOM_IMPACT_REPORT, buildingIds, }; } -export function customBpImpactReportsLoaded(bpImpactReport) { +export function customImpactReportsLoaded(impactReport) { return { - type: LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS, - payload: bpImpactReport.data, + type: LOAD_CUSTOM_IMPACT_REPORT_SUCCESS, + payload: impactReport.data, }; } -export function customBpImpactReportsLoadingError(error) { +export function customImpactReportsLoadingError(error) { return { - type: LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR, + type: LOAD_CUSTOM_IMPACT_REPORT_ERROR, error, }; } diff --git a/src/containers/Reports/components/BpImpact/BpImpact.js b/src/containers/Reports/components/Impact/Impact.js similarity index 77% rename from src/containers/Reports/components/BpImpact/BpImpact.js rename to src/containers/Reports/components/Impact/Impact.js index 45d60702b2f6868e2e68b894b74cf65eddc49425..82b91f57cadb6eabf83a10628c6ff8e90d4c414c 100644 --- a/src/containers/Reports/components/BpImpact/BpImpact.js +++ b/src/containers/Reports/components/Impact/Impact.js @@ -2,11 +2,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReactTooltip from 'react-tooltip'; -import { bpImpactReportPropTypes } from '../../propTypes'; +import { impactReportPropTypes } from '../../propTypes'; import Loading from '../../../../components/Loading'; -export default class BpImpact extends Component { +export default class Impact extends Component { state = {} @@ -14,15 +14,15 @@ export default class BpImpact extends Component { if (this.props.loading) { return ; } - const bpImpactReports = this.props.bpImpactReports; - if (!this.props.bpImpactReports) { + const impactReports = this.props.impactReports; + if (!this.props.impactReports) { return null; } const dateCompareString = ` The blue number represents the difference between the last two generated reports
The last reports were generated on - ${bpImpactReports[0].created} and ${bpImpactReports.length > 1 ? bpImpactReports[1].created : 'n/a'} + ${impactReports[0].created} and ${impactReports.length > 1 ? impactReports[1].created : 'n/a'} `; return (
@@ -30,10 +30,10 @@ export default class BpImpact extends Component { { - bpImpactReports[0].report.map((val, index) => { + impactReports[0].report.map((val, index) => { let change = 0; - if (bpImpactReports.length > 1) { - const previousReport = bpImpactReports[1]; + if (impactReports.length > 1) { + const previousReport = impactReports[1]; change = val.value - previousReport.report[index].value; } let changeString = ''; @@ -75,7 +75,7 @@ export default class BpImpact extends Component { } } -BpImpact.propTypes = { - bpImpactReports: bpImpactReportPropTypes, +Impact.propTypes = { + impactReports: impactReportPropTypes, loading: PropTypes.bool, }; diff --git a/src/containers/Reports/components/BpImpact/BpImpactWrapper.js b/src/containers/Reports/components/Impact/ImpactWrapper.js similarity index 50% rename from src/containers/Reports/components/BpImpact/BpImpactWrapper.js rename to src/containers/Reports/components/Impact/ImpactWrapper.js index eb48e2d469dfa93a1bddaa1c9cdec9d9e47f15f6..f0b4f0424a07574585ae4bace353ce3f3b70b730 100644 --- a/src/containers/Reports/components/BpImpact/BpImpactWrapper.js +++ b/src/containers/Reports/components/Impact/ImpactWrapper.js @@ -1,15 +1,15 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import ReactTooltip from 'react-tooltip'; +import { UncontrolledTooltip } from 'reactstrap'; import { Icon } from 'react-fa'; -import { bpImpactReportPropTypes } from '../../propTypes'; +import { impactReportPropTypes } from '../../propTypes'; import { BGroup } from '../../../../containers/BGroup'; import userPropType from '../../../../containers/User/propTypes'; -import BpImpact from './BpImpact'; +import Impact from './Impact'; -export default class BpImpactWrapper extends Component { +export default class ImpactWrapper extends Component { state = { filterReport: null, @@ -17,9 +17,9 @@ export default class BpImpactWrapper extends Component { componentWillReceiveProps(nextProps) { if ( - nextProps.customBpImpactReport !== this.props.customBpImpactReport + nextProps.customImpactReport !== this.props.customImpactReport ) { - this.setState({ filterReport: [nextProps.customBpImpactReport[0]] }); + this.setState({ filterReport: [nextProps.customImpactReport[0]] }); } } @@ -28,69 +28,66 @@ export default class BpImpactWrapper extends Component { } updateFilterReport = (filter, tableState) => { - const buildingIds = tableState.sortedData.map(val => ( - val._original.building_id // eslint-disable-line - )); - clearTimeout(this.updateFilterReportTimeout); - this.updateFilterReportTimeout = setTimeout( - () => { - this.props.loadCustomBpImpactReport(buildingIds); - }, - 250, - ); + if (this.props.user.permissions['read::CustomImpact']) { + const buildingIds = tableState.sortedData.map(val => ( + val._original.building_id // eslint-disable-line + )); + clearTimeout(this.updateFilterReportTimeout); + this.updateFilterReportTimeout = setTimeout( + () => { + this.props.loadCustomImpactReport(buildingIds); + }, + 250, + ); + } } render() { - if (this.props.bpImpactReports !== null && this.props.bpImpactReports.length < 1) { + if (this.props.impactReports !== null && this.props.impactReports.length < 1) { return
No report
; } if (!this.props.detail) { return ( - ); } - const filterDescriptionString = ` - Filter buildings below to see a new report generated -
- for the subset of buildings that pass the filter - `; return (
-

Overall Report

-
-
+

- + {' '} Filter Report + + Filter buildings below to see a new report generated + for the subset of buildings that pass the filter. +

-

-

BlocPower Buildings

+

Buildings

+ ); + break; + case 'MOS Impact': + loading = ( + this.props.report.cbraImpactReports === null || + this.props.report.loadingCbraImpact + ); + content = ( + ); break; - case 'lighting': + case 'Lighting': loading = ( this.props.report.lightingReports === null || this.props.report.loadingLighting @@ -81,40 +118,50 @@ class Reports extends Component { content = 'There was an error'; } if ( - (this.state.reportTab === 'lighting' && !this.props.user.permissions['read::KissflowLighting']) || - (this.state.reportTab === 'bpimpact' && !this.props.user.permissions['read::BpImpact']) + (this.state.reportTab === 'Lighting' && !this.props.user.permissions['read::KissflowLighting']) || + (this.state.reportTab === 'BP Impact' && !this.props.user.permissions['read::BpImpact']) || + (this.state.reportTab === 'MOS Impact' && !this.props.user.permissions['read::CbraImpact']) ) { content = 'You do not have the permission to view this report'; } return ( // create a simple report that is engaged, out to bid, complete, total active, total inactive -
- +
-
+
{content}
@@ -128,7 +175,8 @@ Reports.propTypes = { report: reportsPropTypes, loadLightingReports: PropTypes.func, loadBpImpactReports: PropTypes.func, - loadCustomBpImpactReport: PropTypes.func, + loadCbraImpactReports: PropTypes.func, + loadCustomImpactReport: PropTypes.func, mode: PropTypes.string, user: userPropType, }; @@ -141,7 +189,8 @@ function mapDispatchToProps(dispatch) { return bindActionCreators({ loadLightingReports, loadBpImpactReports, - loadCustomBpImpactReport, + loadCbraImpactReports, + loadCustomImpactReport, }, dispatch); } diff --git a/src/containers/Reports/propTypes.js b/src/containers/Reports/propTypes.js index 07c1c8819a29ea7aede6de2b950cc56ab59d2543..68655ffb6343c5fa4c6d96bea00e537acdbb9fda 100644 --- a/src/containers/Reports/propTypes.js +++ b/src/containers/Reports/propTypes.js @@ -20,7 +20,7 @@ export const lightingReportPropTypes = arrayOf(shape({ created: string, })); -export const bpImpactReportPropTypes = arrayOf(shape({ +export const impactReportPropTypes = arrayOf(shape({ report: arrayOf(shape({ col: string, value: number, @@ -31,6 +31,7 @@ export const bpImpactReportPropTypes = arrayOf(shape({ export default shape({ lightingReports: lightingReportPropTypes, - bpImpactReports: bpImpactReportPropTypes, + bpImpactReports: impactReportPropTypes, + cbraImpactReports: impactReportPropTypes, ...loadErrorPropTypes, }); diff --git a/src/containers/Reports/reducer.js b/src/containers/Reports/reducer.js index cb4472d7fbcc1d36e384ba8b9b466f1d7ce4bf4f..2a33b01faaae1b5cf543211304af4926abde1187 100644 --- a/src/containers/Reports/reducer.js +++ b/src/containers/Reports/reducer.js @@ -5,9 +5,12 @@ import { LOAD_BP_IMPACT_REPORT, LOAD_BP_IMPACT_REPORT_SUCCESS, LOAD_BP_IMPACT_REPORT_ERROR, - LOAD_CUSTOM_BP_IMPACT_REPORT, - LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS, - LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR, + LOAD_CBRA_IMPACT_REPORT, + LOAD_CBRA_IMPACT_REPORT_SUCCESS, + LOAD_CBRA_IMPACT_REPORT_ERROR, + LOAD_CUSTOM_IMPACT_REPORT, + LOAD_CUSTOM_IMPACT_REPORT_SUCCESS, + LOAD_CUSTOM_IMPACT_REPORT_ERROR, } from './constants'; const initState = { @@ -15,8 +18,10 @@ const initState = { loadingLighting: false, bpImpactReports: null, loadingBpImpact: false, - customBpImpactReport: null, - loadingCustomBpImpact: false, + cbraImpactReports: null, + loadingCbraImpact: false, + customImpactReport: null, + loadingCustomImpact: false, error: false, }; @@ -60,23 +65,42 @@ export default function (state = initState, action) { loadingBpImpact: false, error: action.error, }; - case LOAD_CUSTOM_BP_IMPACT_REPORT: + case LOAD_CBRA_IMPACT_REPORT: return { ...state, - loadingCustomBpImpact: true, + loadingCbraImpact: true, error: false, }; - case LOAD_CUSTOM_BP_IMPACT_REPORT_SUCCESS: + case LOAD_CBRA_IMPACT_REPORT_SUCCESS: return { ...state, - customBpImpactReport: action.payload, - loadingCustomBpImpact: false, + cbraImpactReports: action.payload, + loadingCbraImpact: false, error: false, }; - case LOAD_CUSTOM_BP_IMPACT_REPORT_ERROR: + case LOAD_CBRA_IMPACT_REPORT_ERROR: return { ...state, - loadingCustomBpImpact: false, + loadingCbraImpact: false, + error: action.error, + }; + case LOAD_CUSTOM_IMPACT_REPORT: + return { + ...state, + loadingCustomImpact: true, + error: false, + }; + case LOAD_CUSTOM_IMPACT_REPORT_SUCCESS: + return { + ...state, + customImpactReport: action.payload, + loadingCustomImpact: false, + error: false, + }; + case LOAD_CUSTOM_IMPACT_REPORT_ERROR: + return { + ...state, + loadingCustomImpact: false, error: action.error, }; default: diff --git a/src/containers/Reports/sagas.js b/src/containers/Reports/sagas.js index 8b3665c20aef1c15f9ffe8b83016922c1c04671c..0ea4593f53cecbf00bab2f4b1d5a44dbf2190376 100644 --- a/src/containers/Reports/sagas.js +++ b/src/containers/Reports/sagas.js @@ -1,11 +1,15 @@ import { call, put, takeLatest } from 'redux-saga/effects'; import request from '../../utils/request'; -import { getHeaders, lightingReportURL, bpImpactReportURL } from '../../utils/restServices'; +import { + getHeaders, lightingReportURL, bpImpactReportURL, + cbraImpactReportURL, customImpactReportURL, +} from '../../utils/restServices'; import { LOAD_LIGHTING_REPORT, LOAD_BP_IMPACT_REPORT, - LOAD_CUSTOM_BP_IMPACT_REPORT, + LOAD_CBRA_IMPACT_REPORT, + LOAD_CUSTOM_IMPACT_REPORT, } from './constants'; import { @@ -13,8 +17,10 @@ import { lightingReportsLoadingError, bpImpactReportsLoaded, bpImpactReportsLoadingError, - customBpImpactReportsLoaded, - customBpImpactReportsLoadingError, + cbraImpactReportsLoaded, + cbraImpactReportsLoadingError, + customImpactReportsLoaded, + customImpactReportsLoadingError, } from './actions'; /** @@ -65,22 +71,42 @@ function* getBpImpactReports(action) { } } -function* getCustomBpImpactReport(action) { +function* getCbraImpactReports(action) { + let filterString = ''; + if (action.limit !== -1) { + filterString = `limit=${action.limit}&order_by=created&sort=desc`; + } + const res = yield call( + request, + `${cbraImpactReportURL}?${filterString}`, { + method: 'GET', + headers: getHeaders(), + } + ); + + if (!res.err) { + yield put(cbraImpactReportsLoaded(res)); + } else { + yield put(cbraImpactReportsLoadingError(res.err)); + } +} + +function* getCustomImpactReport(action) { const filterString = action.buildingIds.reduce((acc, val) => ( `${acc}building_id=${val}&` ), ''); const res = yield call( request, - `${bpImpactReportURL}?generate=true&${filterString}`, { + `${customImpactReportURL}?${filterString}`, { method: 'GET', headers: getHeaders(), } ); if (!res.err) { - yield put(customBpImpactReportsLoaded(res)); + yield put(customImpactReportsLoaded(res)); } else { - yield put(customBpImpactReportsLoadingError(res.err)); + yield put(customImpactReportsLoadingError(res.err)); } } @@ -91,7 +117,8 @@ function* getCustomBpImpactReport(action) { function* reportWatcher() { yield takeLatest(LOAD_LIGHTING_REPORT, getLightingReports); yield takeLatest(LOAD_BP_IMPACT_REPORT, getBpImpactReports); - yield takeLatest(LOAD_CUSTOM_BP_IMPACT_REPORT, getCustomBpImpactReport); + yield takeLatest(LOAD_CBRA_IMPACT_REPORT, getCbraImpactReports); + yield takeLatest(LOAD_CUSTOM_IMPACT_REPORT, getCustomImpactReport); } export default reportWatcher; diff --git a/src/utils/restServices.js b/src/utils/restServices.js index fc7e675b2f244e46e9f784883328766be6bbda96..ce0de66345e66057b37b3c08b3e6067739ec452c 100644 --- a/src/utils/restServices.js +++ b/src/utils/restServices.js @@ -40,6 +40,8 @@ export const sfBuildingImpactURL = `${projectService}/sfbuildingimpact/`; export const lightingReportURL = `${reportService}/kissflowlighting/`; export const bpImpactReportURL = `${reportService}/bpimpact/`; +export const cbraImpactReportURL = `${reportService}/cbraimpact/`; +export const customImpactReportURL = `${reportService}/customimpact/`; export const gatewayURL = `${iotService}/gateway/`; export const sensewareNodeURL = `${iotService}/sensewarenode/`;