Skip to content

Commit

Permalink
feat(dashboard): add significant digits configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbuss committed Jun 23, 2023
1 parent 41cba0e commit bcc5c51
Show file tree
Hide file tree
Showing 32 changed files with 394 additions and 14 deletions.
11 changes: 10 additions & 1 deletion packages/dashboard/src/components/actions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ export type ActionsProps = {
grid: DashboardState['grid'];
readOnly: boolean;
dashboardConfiguration: DashboardState['dashboardConfiguration'];
significantDigits: DashboardState['significantDigits'];
onSave?: DashboardSave;
editable?: boolean;
};

const Actions: React.FC<ActionsProps> = ({ dashboardConfiguration, editable, grid, readOnly, onSave }) => {
const Actions: React.FC<ActionsProps> = ({
dashboardConfiguration,
significantDigits,
editable,
grid,
readOnly,
onSave,
}) => {
const [dashboardSettingsVisible, setDashboardSettingsVisible] = useState(false);
const dispatch = useDispatch();

Expand All @@ -27,6 +35,7 @@ const Actions: React.FC<ActionsProps> = ({ dashboardConfiguration, editable, gri
numColumns: grid.width,
numRows: grid.height,
cellSize: grid.stretchToFit ? undefined : grid.cellSize,
significantDigits,
},
...dashboardConfiguration,
});
Expand Down
15 changes: 10 additions & 5 deletions packages/dashboard/src/components/actions/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React from 'react';

import { Box, Checkbox, Modal, NonCancelableCustomEvent, SpaceBetween } from '@cloudscape-design/components';
import { BaseChangeDetail } from '@cloudscape-design/components/input/interfaces';
import { Box, Checkbox, Modal, SpaceBetween } from '@cloudscape-design/components';

import LabeledInput from '../util/labeledInput';
import { useGridSettings } from './useGridSettings';

// Should never return NaN
const numberFromDetail = (event: NonCancelableCustomEvent<BaseChangeDetail>) => parseInt(event.detail.value) || 0;
import { numberFromDetail } from '~/util/inputEvent';

export type DashboardSettingsProps = {
onClose: () => void;
Expand All @@ -20,16 +17,24 @@ const DashboardSettings: React.FC<DashboardSettingsProps> = ({ onClose, isVisibl
columns,
cellSize,
stretchToFit,
significantDigits,
onChangeCellSize,
onChangeNumberOfColumns,
onChangeNumberOfRows,
onToggleStretchToFit,
onChangeSignificantDigits,
} = useGridSettings();

return (
<Modal onDismiss={onClose} visible={isVisible} closeAriaLabel='Close modal' header='Dashboard Settings'>
<Box>
<SpaceBetween direction='vertical' size='l'>
<LabeledInput
label='Decimal Places'
type='number'
value={significantDigits.toFixed()}
onChange={(event) => onChangeSignificantDigits(numberFromDetail(event))}
/>
<Checkbox onChange={({ detail }) => onToggleStretchToFit(detail.checked)} checked={stretchToFit}>
Stretch grid to fit screen size.
</Checkbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ it('has initial values', () => {
expect(result.current.cellSize).toBe(initialState.grid.cellSize);
expect(result.current.stretchToFit).toBe(initialState.grid.stretchToFit);
expect(result.current.stretchToFit).toBe(initialState.grid.stretchToFit);
expect(result.current.significantDigits).toBe(initialState.significantDigits);
});

it('has can change values', () => {
Expand Down Expand Up @@ -51,4 +52,10 @@ it('has can change values', () => {
result.current.onToggleStretchToFit(!initialStretchToFit);
});
expect(result.current.stretchToFit).toBe(!initialStretchToFit);

const initialSignificantDigits = initialState.significantDigits;
act(() => {
result.current.onChangeSignificantDigits(initialSignificantDigits + 1);
});
expect(result.current.significantDigits).toBe(initialSignificantDigits + 1);
});
7 changes: 7 additions & 0 deletions packages/dashboard/src/components/actions/useGridSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
onChangeDashboardStretchToFitAction,
onChangeDashboardWidthAction,
} from '~/store/actions';
import { onUpdateSignificantDigitsAction } from '~/store/actions/updateSignificantDigits';

export const useGridSettings = () => {
const dispatch = useDispatch();

const { width, height, cellSize, stretchToFit } = useSelector((state: DashboardState) => state.grid);
const significantDigits = useSelector((state: DashboardState) => state.significantDigits);

const onToggleStretchToFit = (updatedStretchToFit: boolean) => {
dispatch(onChangeDashboardStretchToFitAction({ stretchToFit: updatedStretchToFit }));
Expand All @@ -25,15 +27,20 @@ export const useGridSettings = () => {
const onChangeCellSize = (updatedCellSize: number) => {
dispatch(onChangeDashboardCellSizeAction({ cellSize: updatedCellSize }));
};
const onChangeSignificantDigits = (updatedSignificantDigits: number) => {
dispatch(onUpdateSignificantDigitsAction({ significantDigits: updatedSignificantDigits }));
};

return {
rows: height,
columns: width,
cellSize,
stretchToFit,
significantDigits,
onToggleStretchToFit,
onChangeCellSize,
onChangeNumberOfColumns,
onChangeNumberOfRows,
onChangeSignificantDigits,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ it('saves when the save button is pressed with default grid settings provided',
cellSize: 20,
numColumns: 100,
numRows: 100,
significantDigits: 4,
},
})
);
Expand Down Expand Up @@ -72,6 +73,7 @@ it('saves when the save button is pressed with default grid settings provided',
cellSize: undefined,
numColumns: 100,
numRows: 100,
significantDigits: 4,
},
})
);
Expand Down
3 changes: 3 additions & 0 deletions packages/dashboard/src/components/internalDashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const InternalDashboard: React.FC<InternalDashboardProperties> = ({ onSave, edit
const copiedWidgets = useSelector((state: DashboardState) => state.copiedWidgets);
const readOnly = useSelector((state: DashboardState) => state.readOnly);
const selectedWidgets = useSelectedWidgets();
const significantDigits = useSelector((state: DashboardState) => state.significantDigits);

const [viewFrame, setViewFrameElement] = useState<HTMLDivElement | undefined>(undefined);

Expand Down Expand Up @@ -203,6 +204,7 @@ const InternalDashboard: React.FC<InternalDashboardProperties> = ({ onSave, edit
onSave={onSave}
dashboardConfiguration={dashboardConfiguration}
grid={grid}
significantDigits={significantDigits}
editable={editable}
/>
</>
Expand Down Expand Up @@ -237,6 +239,7 @@ const InternalDashboard: React.FC<InternalDashboardProperties> = ({ onSave, edit
onSave={onSave}
dashboardConfiguration={dashboardConfiguration}
grid={grid}
significantDigits={significantDigits}
editable={editable}
/>
</SpaceBetween>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { AxisSettingsConfiguration } from './axisSettings';
import { ThresholdSettingsConfiguration } from './thresholdSettings';
import { PropertiesAndAlarmsSettingsConfiguration } from './propertiesAndAlarmsSettings';
import { AggregationsSettingsConfiguration } from './aggregationSettings';
import { SettingsConfiguration } from './settings';

export const propertiesSections = [
<SizeAndPositionConfiguration />,
<PropertiesAndAlarmsSettingsConfiguration />,
<ThresholdSettingsConfiguration />,
<AggregationsSettingsConfiguration />,
<AxisSettingsConfiguration />,
<SettingsConfiguration />,
<TextSettingsConfiguration />,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

import { PropertiesSection } from '~/customization/propertiesSectionComponent';
import { DashboardWidget } from '~/types';
import { maybeWithDefault } from '~/util/maybe';
import { SettingsSection } from './section';
import { CommonChartProperties } from '~/customization/widgets/types';
import { nonNegative } from '~/util/number';

const isSettingsWidget = (w: DashboardWidget): w is DashboardWidget<CommonChartProperties> =>
'queryConfig' in w.properties;

export const SettingsConfiguration: React.FC = () => (
<PropertiesSection
isVisible={isSettingsWidget}
render={({ useProperty }) => {
const [significantDigits, updateSignificantDigits] = useProperty(
(properties) => properties.significantDigits,
(properties, updatedSignificantDigits) => ({
...properties,
significantDigits: updatedSignificantDigits && nonNegative(updatedSignificantDigits),
})
);

return (
<SettingsSection
significantDigits={maybeWithDefault(undefined, significantDigits)}
updateSignificantDigits={updateSignificantDigits}
/>
);
}}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.settings-property-label {
display: flex;
align-items: center;
flex-wrap: wrap;
}

.settings-property-label-control {
flex-grow: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MOCK_LINE_CHART_WIDGET } from '../../../../testing/mocks';
import { Provider } from 'react-redux';
import { configureDashboardStore } from '~/store';
import { getByLabelText, render } from '@testing-library/react';
import React from 'react';
import type { DashboardState } from '~/store/state';
import { SettingsConfiguration } from './index';

const state: Partial<DashboardState> = {
dashboardConfiguration: {
widgets: [MOCK_LINE_CHART_WIDGET],
viewport: { duration: '5m' },
},
selectedWidgets: [MOCK_LINE_CHART_WIDGET],
};

const TestComponent = () => (
<Provider store={configureDashboardStore(state)}>
<SettingsConfiguration />
</Provider>
);

it('renders', () => {
const elem = render(<TestComponent />).baseElement;
expect(elem).toBeTruthy();
});

it('renders the decimal places input', async () => {
const elem = render(<TestComponent />).baseElement;
expect(getByLabelText(elem, 'decimal places')).toBeTruthy();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';

import { ExpandableSection, Input, InputProps, SpaceBetween } from '@cloudscape-design/components';
import { NonCancelableEventHandler } from '@cloudscape-design/components/internal/events';

import * as awsui from '@cloudscape-design/design-tokens';
import { numberFromDetail } from '~/util/inputEvent';

import './section.css';
import { isNumeric } from '@iot-app-kit/core/dist/es/common/number';

export const SettingsSection = ({
significantDigits,
updateSignificantDigits,
}: {
significantDigits: number | undefined;
updateSignificantDigits: (newValue: number | undefined) => void;
}) => {
const onSignificantDigitsChange: NonCancelableEventHandler<InputProps.ChangeDetail> = (event) => {
updateSignificantDigits(isNumeric(event.detail.value) ? numberFromDetail(event) : undefined);
};
return (
<ExpandableSection headerText='Settings' defaultExpanded>
<SpaceBetween size='m' direction='vertical'>
<div className='settings-property-label' style={{ gap: awsui.spaceScaledS }}>
<label htmlFor='decimal-places'>Decimal places</label>
<div className='settings-property-label-control'>
<Input
type='number'
controlId='decimal-places'
ariaLabel='decimal places'
value={significantDigits?.toFixed() ?? ''}
onChange={onSignificantDigitsChange}
/>
</div>
</div>
</SpaceBetween>
</ExpandableSection>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@ import { aggregateToString } from '~/customization/propertiesSections/aggregatio
const BarChartWidgetComponent: React.FC<BarChartWidget> = (widget) => {
const viewport = useSelector((state: DashboardState) => state.dashboardConfiguration.viewport);
const readOnly = useSelector((state: DashboardState) => state.readOnly);
const dashboardSignificantDigits = useSelector((state: DashboardState) => state.significantDigits);

const { queryConfig, styleSettings, axis, thresholdSettings, thresholds } = widget.properties;
const {
queryConfig,
styleSettings,
axis,
thresholdSettings,
thresholds,
significantDigits: widgetSignificantDigits,
} = widget.properties;

const { iotSiteWiseQuery } = useQueries();

const queries = iotSiteWiseQuery && queryConfig.query ? [iotSiteWiseQuery?.timeSeriesData(queryConfig.query)] : [];
const aggregation = getAggregation(queryConfig);

const significantDigits = widgetSignificantDigits ?? dashboardSignificantDigits;

return (
<BarChart
queries={queries}
Expand All @@ -30,6 +40,7 @@ const BarChartWidgetComponent: React.FC<BarChartWidget> = (widget) => {
styles={styleSettings}
thresholds={thresholds}
thresholdSettings={thresholdSettings}
significantDigits={significantDigits}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { aggregateToString } from '~/customization/propertiesSections/aggregatio

const KPIWidgetComponent: React.FC<KPIWidget> = (widget) => {
const viewport = useSelector((state: DashboardState) => state.dashboardConfiguration.viewport);
const dashboardSignificantDigits = useSelector((state: DashboardState) => state.significantDigits);

const {
styleSettings,
Expand All @@ -26,6 +27,7 @@ const KPIWidgetComponent: React.FC<KPIWidget> = (widget) => {
showName,
showTimestamp,
thresholds,
significantDigits: widgetSignificantDigits,
} = widget.properties;

const { iotSiteWiseQuery } = useQueries();
Expand All @@ -52,6 +54,8 @@ const KPIWidgetComponent: React.FC<KPIWidget> = (widget) => {
isDefined
);

const significantDigits = widgetSignificantDigits ?? dashboardSignificantDigits;

return (
<KPI
query={query}
Expand All @@ -60,6 +64,7 @@ const KPIWidgetComponent: React.FC<KPIWidget> = (widget) => {
settings={settings}
thresholds={thresholds}
aggregationType={aggregateToString(aggregation)}
significantDigits={significantDigits}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@ import { aggregateToString } from '~/customization/propertiesSections/aggregatio
const LineChartWidgetComponent: React.FC<LineChartWidget> = (widget) => {
const viewport = useSelector((state: DashboardState) => state.dashboardConfiguration.viewport);
const readOnly = useSelector((state: DashboardState) => state.readOnly);
const dashboardSignificantDigits = useSelector((state: DashboardState) => state.significantDigits);

const { queryConfig, styleSettings, axis, thresholds, thresholdSettings } = widget.properties;
const {
queryConfig,
styleSettings,
axis,
thresholds,
thresholdSettings,
significantDigits: widgetSignificantDigits,
} = widget.properties;

const { iotSiteWiseQuery } = useQueries();
const queries = iotSiteWiseQuery && queryConfig.query ? [iotSiteWiseQuery?.timeSeriesData(queryConfig.query)] : [];

const aggregation = getAggregation(queryConfig);

const significantDigits = widgetSignificantDigits ?? dashboardSignificantDigits;

return (
<LineChart
queries={queries}
Expand All @@ -30,6 +40,7 @@ const LineChartWidgetComponent: React.FC<LineChartWidget> = (widget) => {
styles={styleSettings}
thresholds={thresholds}
thresholdSettings={thresholdSettings}
significantDigits={significantDigits}
/>
);
};
Expand Down

0 comments on commit bcc5c51

Please sign in to comment.