Skip to content

Commit

Permalink
Merge remote-tracking branch 'ChrisMart21/rtk' into pr/ChrisMart21/1113
Browse files Browse the repository at this point in the history
  • Loading branch information
huss committed Feb 13, 2024
2 parents ccb5310 + 6059a82 commit 8d51cf0
Show file tree
Hide file tree
Showing 18 changed files with 182 additions and 165 deletions.
3 changes: 1 addition & 2 deletions src/client/app/components/DateRangeComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import translate from '../utils/translate';
import TooltipMarkerComponent from './TooltipMarkerComponent';
import { selectSelectedLanguage } from '../redux/slices/appStateSlice';

// Potential Fixes, for now omitted
// import '../styles/DateRangeCustom.css'
import '../styles/DateRangeCustom.css'

/**
* A component which allows users to select date ranges in lieu of a slider (line graphic)
Expand Down
3 changes: 3 additions & 0 deletions src/client/app/components/MeterAndGroupSelectComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export default function MeterAndGroupSelectComponent(props: MeterAndGroupSelectP
// Included React-Select Animations
components={animatedComponents}
styles={customStyles}
classNames={{
valueContainer: () => 'no_scrollbar'
}}
isLoading={somethingIsFetching}
/>
</>
Expand Down
12 changes: 7 additions & 5 deletions src/client/app/components/ThreeDComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,19 @@ export default function ThreeDComponent() {
}

return (
<div style={{ width: '100%', height: '100%' }}>
<>
<ThreeDPillComponent />
{isFetching
? <SpinnerComponent loading width={50} height={50} />
: <Plot
data={dataToRender as Plotly.Data[]}
layout={layout as Plotly.Layout}
config={config}
style={{ width: '100%', height: '100%' }}
style={{ width: '100%', flexGrow: '1' }}
useResizeHandler={true}
/>
}
</div>
</>
)
}

Expand Down Expand Up @@ -221,7 +221,9 @@ function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: numbe
function setThreeDLayout(zLabelText: string = 'Resource Usage') {
// responsible for setting Labels
return {
//Leaves holes in graph for missing, undefined, NaN, or null values
// Eliminate margin
margin: { t: 0, b: 0, l: 0, r: 0, pad: 0 },
// Leaves gaps / voids in graph for missing, undefined, NaN, or null values
connectgaps: false,
scene: {
xaxis: {
Expand All @@ -248,7 +250,7 @@ function setThreeDLayout(zLabelText: string = 'Resource Usage') {
}
}
}
}
} as Partial<Plotly.Layout>
}

const config = {
Expand Down
2 changes: 1 addition & 1 deletion src/client/app/components/UIOptionsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default function UIOptionsComponent() {

ReactTooltip.rebuild();
return (
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', overflow: 'scroll' }} ref={optionsRef}>
<div className='no_scrollbar' style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-start', overflow: 'scroll' }} ref={optionsRef}>
<ChartSelectComponent />
<ChartDataSelectComponent />
<GraphicRateMenuComponent />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
const handleClose = () => {
setShowEditModal(false);
}
React.useEffect(() => undefined, [props.conversion])
// Create header from sourceId, destinationId identifiers
// Arrow is bidirectional if conversion is bidirectional and one way if not.
let arrowShown: string;
Expand All @@ -46,7 +45,7 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
} else {
arrowShown = ' → ';
}
const header = String(unitDataById[props.conversion.sourceId].identifier + arrowShown + unitDataById[props.conversion.destinationId].identifier);
const header = String(unitDataById[props.conversion.sourceId]?.identifier + arrowShown + unitDataById[props.conversion.destinationId]?.identifier);

// Unlike the details component, we don't check if units are loaded since must come through that page.

Expand All @@ -56,10 +55,10 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
{header}
</div>
<div className="item-container">
<b><FormattedMessage id="conversion.source" /></b> {unitDataById[props.conversion.sourceId].identifier}
<b><FormattedMessage id="conversion.source" /></b> {unitDataById[props.conversion.sourceId]?.identifier}
</div>
<div className="item-container">
<b><FormattedMessage id="conversion.destination" /></b> {unitDataById[props.conversion.destinationId].identifier}
<b><FormattedMessage id="conversion.destination" /></b> {unitDataById[props.conversion.destinationId]?.identifier}
</div>
<div className={props.conversion.bidirectional.toString()}>
<b><FormattedMessage id="conversion.bidirectional" /></b> {translate(`TrueFalseType.${props.conversion.bidirectional.toString()}`)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { ConversionData } from '../../types/redux/conversions';
import TooltipMarkerComponent from '../TooltipMarkerComponent';
import ConversionViewComponent from './ConversionViewComponent';
import CreateConversionModalComponent from './CreateConversionModalComponent';
import { UnitDataById } from 'types/redux/units';

const stableEmptyConversions: ConversionData[] = []
const stableEmptyUnitDataById: UnitDataById = {}
/**
* Defines the conversions page card view
* @returns Conversion page element
Expand All @@ -21,9 +24,9 @@ export default function ConversionsDetailComponent() {
// The route stops you from getting to this page if not an admin.

// Conversions state
const { data: conversionsState = [], isFetching: conversionsFetching } = conversionsApi.useGetConversionsDetailsQuery();
const { data: conversionsState = stableEmptyConversions, isFetching: conversionsFetching } = conversionsApi.useGetConversionsDetailsQuery();
// Units DataById
const { unitDataById = {}, isFetching: unitsFetching } = unitsApi.useGetUnitsDetailsQuery(undefined, {
const { unitDataById = stableEmptyUnitDataById, isFetching: unitsFetching } = unitsApi.useGetUnitsDetailsQuery(undefined, {
selectFromResult: ({ data, ...result }) => ({
...result,
unitDataById: data && unitsAdapter.getSelectors().selectEntities(data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC
id='sourceId'
name='sourceId'
type='text'
defaultValue={unitDataById[state.sourceId].identifier}
defaultValue={unitDataById[state.sourceId]?.identifier}
// Disable input to prevent changing ID value
disabled>
</Input>
Expand All @@ -176,7 +176,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC
id='destinationId'
name='destinationId'
type='text'
defaultValue={unitDataById[state.destinationId].identifier}
defaultValue={unitDataById[state.destinationId]?.identifier}
// Disable input to prevent changing ID value
disabled>
</Input>
Expand Down
2 changes: 0 additions & 2 deletions src/client/app/components/groups/EditGroupModalComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr
// changes were made to the children. This avoid a reload on name change, etc.
submitGroupEdits(submitState)
});
// The next line is unneeded since do refresh.
// dispatch(removeUnsavedChanges());
} else {
showErrorNotification(translate('group.input.error'));
}
Expand Down
118 changes: 14 additions & 104 deletions src/client/app/components/router/GraphLinkComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,122 +2,32 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PayloadAction } from '@reduxjs/toolkit';
import InitializingComponent from '../router/InitializingComponent';
import moment from 'moment';
import * as React from 'react';
import { Navigate, useSearchParams } from 'react-router-dom';
import { graphSlice } from '../../redux/slices/graphSlice';
import { useWaitForInit } from '../../redux/componentHooks';
import { useAppDispatch } from '../../redux/reduxHooks';
import { validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
import { AreaUnitType } from '../../utils/getAreaUnitConversion';
import { showErrorNotification } from '../../utils/notifications';
import translate from '../../utils/translate';
import { TimeInterval } from '../../../../common/TimeInterval';
import { ChartTypes, LineGraphRate, MeterOrGroup } from '../../types/redux/graph';
import { changeSelectedMap } from '../../redux/actions/map';
import { appStateSlice } from '../../redux/slices/appStateSlice';
import { processGraphLink } from '../../redux/slices/graphSlice';
import InitializingComponent from '../router/InitializingComponent';

export const GraphLink = () => {
const dispatch = useAppDispatch();
const [URLSearchParams] = useSearchParams();
const { initComplete } = useWaitForInit();
const dispatchQueue: PayloadAction<any>[] = [];
React.useEffect(() => {
const linkIsValid = validateHotlink(URLSearchParams)
if (linkIsValid) {
dispatch(processGraphLink(URLSearchParams))
}
}, [])

if (!initComplete) {
return <InitializingComponent />
}

try {
URLSearchParams.forEach((value, key) => {
// TODO Needs to be refactored into a single dispatch/reducer pair.
// It is a best practice to reduce the number of dispatch calls, so this logic should be converted into a single reducer for the graphSlice
// TODO validation could be implemented across all cases similar to compare period and sorting order
switch (key) {
case 'areaNormalization':
dispatchQueue.push(graphSlice.actions.setAreaNormalization(value === 'true'))
break;
case 'areaUnit':
dispatchQueue.push(graphSlice.actions.updateSelectedAreaUnit(value as AreaUnitType))
break;
case 'barDuration':
dispatchQueue.push(graphSlice.actions.updateBarDuration(moment.duration(parseInt(value), 'days')))
break;
case 'barStacking':
dispatchQueue.push(graphSlice.actions.setBarStacking(Boolean(value)))
break;
case 'chartType':
dispatchQueue.push(graphSlice.actions.changeChartToRender(value as ChartTypes))
break;
case 'comparePeriod':
dispatchQueue.push(graphSlice.actions.updateComparePeriod({ comparePeriod: validateComparePeriod(value), currentTime: moment() }))
break;
case 'compareSortingOrder':
dispatchQueue.push(graphSlice.actions.changeCompareSortingOrder(validateSortingOrder(value)))
break;
case 'groupIDs':
dispatchQueue.push(graphSlice.actions.updateSelectedGroups(value.split(',').map(s => parseInt(s))))
break;
case 'mapID':
// 'TODO, Verify Behavior & FIXME! MapLink not working as expected
dispatch(changeSelectedMap(parseInt(value)))
break;
case 'meterIDs':
dispatchQueue.push(graphSlice.actions.updateSelectedMeters(value.split(',').map(s => parseInt(s))))
break;
case 'meterOrGroup':
dispatchQueue.push(graphSlice.actions.updateThreeDMeterOrGroup(value as MeterOrGroup));
break;
case 'meterOrGroupID':
dispatchQueue.push(graphSlice.actions.updateThreeDMeterOrGroupID(parseInt(value)));
break;
case 'minMax':
dispatchQueue.push(graphSlice.actions.setShowMinMax(value === 'true' ? true : false))
break;
case 'optionsVisibility':
dispatchQueue.push(appStateSlice.actions.setOptionsVisibility(value === 'true' ? true : false))
break;
case 'rate':
{
const params = value.split(',');
const rate = { label: params[0], rate: parseFloat(params[1]) } as LineGraphRate;
dispatchQueue.push(graphSlice.actions.updateLineGraphRate(rate))
}
break;
case 'readingInterval':
dispatchQueue.push(graphSlice.actions.updateThreeDReadingInterval(parseInt(value)));
break;
case 'serverRange':
dispatchQueue.push(graphSlice.actions.updateTimeInterval(TimeInterval.fromString(value)));
/**
* commented out since days from present feature is not currently used
*/
// const index = info.indexOf('dfp');
// if (index === -1) {
// options.serverRange = TimeInterval.fromString(info);
// } else {
// const message = info.substring(0, index);
// const stringField = this.getNewIntervalFromMessage(message);
// options.serverRange = TimeInterval.fromString(stringField);
// }
break;
case 'sliderRange':
dispatchQueue.push(graphSlice.actions.changeSliderRange(TimeInterval.fromString(value)));
break;
case 'unitID':
dispatchQueue.push(graphSlice.actions.updateSelectedUnit(parseInt(value)))
break;
default:
throw new Error('Unknown query parameter');
}
})

dispatchQueue.forEach(dispatch)
} catch (err) {
showErrorNotification(translate('failed.to.link.graph'));
}
// All appropriate state updates should've been executed
// redirect to root and clear the link in the search bar
return <Navigate to='/' replace />
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const validateHotlink = (params: URLSearchParams) => {
// TODO VALIDATE HOTLINK
return true
}
3 changes: 2 additions & 1 deletion src/client/app/redux/api/groupsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export const groupsApi = baseApi.injectEndpoints({
url: 'api/groups/edit',
method: 'PUT',
body: group
})
}),
invalidatesTags: ['GroupData', 'GroupChildrenData']
}),
deleteGroup: builder.mutation<void, number>({
query: groupId => ({
Expand Down
3 changes: 2 additions & 1 deletion src/client/app/redux/selectors/lineChartSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export const selectPlotlyMeterData = selectFromLineReadingsResult(
[data => data, (_data, dependencies: PlotlyLineDeps) => dependencies],
(data, { areaUnit, areaNormalization, meterDataById, compatibleEntities, showMinMax, lineUnitLabel, rateScaling }) => {
const plotlyLineData = Object.entries(data)
// filter entries for requested groups
// filter entries for requested compatible groups
// compatible entities is using the same data deriving selectors as the select option for group, & meter.
.filter(([groupID]) => compatibleEntities.includes((Number(groupID))))
.map(([id, readings]) => {
const meterInfo = meterDataById[Number(id)]
Expand Down
20 changes: 18 additions & 2 deletions src/client/app/redux/selectors/plotlyDataSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,25 @@ export const selectBarUnitLabel = (state: RootState) => {
}

// fallback to name if no identifier
export const selectNameFromEntity = (entity: MeterData | GroupData | UnitData) => 'identifier' in entity ? entity.identifier : entity.name
export const selectNameFromEntity = (entity: MeterData | GroupData | UnitData) => {
if (entity && 'identifier' in entity && entity.identifier) {
return entity.identifier
} else if (entity && 'name' in entity && entity.name) {
return entity.name
} else {
// Users May Possibly receive data for meters with neither identifier, or name so empty.
return ''
}
}

// Determines if meter or group base on objet distinct properties of each
export const selectMeterOrGroupFromEntity = (entity: MeterData | GroupData) => 'meterType' in entity ? MeterOrGroup.meters : MeterOrGroup.groups
export const selectMeterOrGroupFromEntity = (entity: MeterData | GroupData) => {
return 'meterType' in entity ? MeterOrGroup.meters : 'childMeters' in entity ? MeterOrGroup.groups : undefined
}

export const selectDefaultGraphicUnitFromEntity = (entity: MeterData | GroupData) => {
return 'defaultGraphicUnit' in entity ? entity.defaultGraphicUnit : undefined
}

// Line and Groups use these values to derive plotly data, so make selector for them to 'extend'
export const selectCommonPlotlyDependencies = createStructuredSelector(
Expand Down
16 changes: 8 additions & 8 deletions src/client/app/redux/selectors/threeDSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { selectGroupDataById } from '../../redux/api/groupsApi';
import { MeterOrGroup } from '../../types/redux/graph';
import { AreaUnitType } from '../../utils/getAreaUnitConversion';
import { selectMeterDataById } from '../../redux/api/metersApi';
import { selectNameFromEntity } from './plotlyDataSelectors';


// Memoized Selectors
Expand All @@ -24,15 +25,14 @@ export const selectThreeDComponentInfo = createSelector(
let isAreaCompatible = true;

if (id && meterDataById[id]) {
const entity = meterOrGroup === MeterOrGroup.meters ? meterDataById[id] : groupDataById[id]
meterOrGroupName = selectNameFromEntity(entity)
// Get Meter or Group's info
if (meterOrGroup === MeterOrGroup.meters && meterDataById) {
const meterInfo = meterDataById[id]
meterOrGroupName = meterInfo.identifier;
isAreaCompatible = meterInfo.area !== 0 && meterInfo.areaUnit !== AreaUnitType.none;
} else if (meterOrGroup === MeterOrGroup.groups && groupDataById) {
const groupInfo = groupDataById[id];
meterOrGroupName = groupInfo.name;
isAreaCompatible = groupInfo.area !== 0 && groupInfo.areaUnit !== AreaUnitType.none;
if (meterOrGroup === MeterOrGroup.meters && entity) {
isAreaCompatible = entity.area !== 0 && entity.areaUnit !== AreaUnitType.none;
} else if (meterOrGroup === MeterOrGroup.groups && entity) {
meterOrGroupName = entity.name;
isAreaCompatible = entity.area !== 0 && entity.areaUnit !== AreaUnitType.none;
}

}
Expand Down
Loading

0 comments on commit 8d51cf0

Please sign in to comment.