Skip to content

Commit

Permalink
Merge pull request #651 from lsst-ts/tickets/DM-45082
Browse files Browse the repository at this point in the history
Add visual cue on CSCDetail and CSCExpanded to identify CSCs on simulation mode
  • Loading branch information
sebastian-aranda authored Jul 10, 2024
2 parents 7061ee3 + 50b5810 commit eaac0d4
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 152 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Version History
===============

v6.2.0
------

* Add visual cue on CSCDetail and CSCExpanded to identify CSCs on simulation mode `<https://github.com/lsst-ts/LOVE-frontend/pull/651>`_

v6.1.1
------

Expand Down
16 changes: 9 additions & 7 deletions love/src/components/CSCSummary/CSCDetail/CSCDetail.container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const CSCDetailContainer = ({
serverTime,
embedded,
withWarning,
simulationMode,
isRaw,
subscriptions,
}) => {
Expand All @@ -112,6 +113,7 @@ const CSCDetailContainer = ({
embedded={embedded}
withWarning={withWarning}
serverTime={serverTime}
simulationMode={simulationMode}
/>
);
};
Expand All @@ -121,6 +123,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
`event-${ownProps.name}-${ownProps.salindex}-summaryState`,
`event-${ownProps.name}-${ownProps.salindex}-logMessage`,
`event-${ownProps.name}-${ownProps.salindex}-errorCode`,
`event-${ownProps.name}-${ownProps.salindex}-simulationMode`,
`event-Heartbeat-0-stream`,
];
return {
Expand All @@ -135,16 +138,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
};

const mapStateToProps = (state, ownProps) => {
const withWarning = getCSCWithWarning(state, ownProps.name, ownProps.salindex);
let summaryStateData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-summaryState`);
let heartbeatData = getCSCHeartbeat(state, ownProps.name, ownProps.salindex);
const serverTime = getServerTime(state);
if (!summaryStateData) {
summaryStateData = {};
}
const withWarning = getCSCWithWarning(state, ownProps.name, ownProps.salindex);
const heartbeatData = getCSCHeartbeat(state, ownProps.name, ownProps.salindex);
const summaryStateData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-summaryState`)?.[0];
const simulationMode = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-simulationMode`)?.[0];

return {
summaryStateData: summaryStateData[0],
summaryStateData,
simulationMode,
heartbeatData,
withWarning,
serverTime,
Expand Down
118 changes: 78 additions & 40 deletions love/src/components/CSCSummary/CSCDetail/CSCDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,48 @@ import styles from './CSCDetail.module.css';

export default class CSCDetail extends Component {
static propTypes = {
/** Name of the CSC */
name: PropTypes.string,
/** Belonging group of the CSC */
group: PropTypes.string,
/** Index of the CSC */
salindex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
data: PropTypes.object,
/** Function to execute when the CSC is clicked
* It will be called with an object containing the following keys:
* - group: The group of the CSC
* - csc: The name of the CSC
* - salindex: The index of the CSC
*/
onCSCClick: PropTypes.func,
/** Heartbeat stream */
heartbeatData: PropTypes.object,
/** Summary State stream */
summaryStateData: PropTypes.object,
/** Function to subscribe to streams */
subscribeToStreams: PropTypes.func,
/** Function to unsubscribe to streams */
unsubscribeToStreams: PropTypes.func,
/** Whereas to apply a minWidth */
embedded: PropTypes.bool,
/* Whether the component should subscribe to streams*/
shouldSubscribe: PropTypes.bool,
isRaw: PropTypes.bool,
/** Make heartbeat display optional */
hasHeartbeat: PropTypes.bool,
/** Whereas to show a warning icon */
withWarning: PropTypes.bool,
/** Server time object */
serverTime: PropTypes.object,
/** Simulation Mode stream */
simulationMode: PropTypes.object,
};

static defaultProps = {
name: '',
group: '',
data: {},
onCSCClick: () => 0,
heartbeatData: null,
summaryStateData: undefined,
onCSCClick: () => {},
subscribeToStreams: () => {},
unsubscribeToStreams: () => {},
embedded: false,
shouldSubscribe: true,
isRaw: false,
hasHeartbeat: true,
withWarning: false,
};

static states = {
Expand Down Expand Up @@ -95,27 +110,42 @@ export default class CSCDetail extends Component {
};

componentDidMount = () => {
this.props.subscribeToStreams(this.props.name, this.props.salindex);
const { name, salindex, subscribeToStreams } = this.props;
subscribeToStreams(name, salindex);
};

componentWillUnmount = () => {
this.props.unsubscribeToStreams(this.props.name, this.props.salindex);
const { name, salindex, unsubscribeToStreams } = this.props;
unsubscribeToStreams(name, salindex);
};

render() {
const { props } = this;
const {
group,
name,
salindex,
summaryStateData,
heartbeatData,
onCSCClick,
embedded,
hasHeartbeat,
withWarning,
serverTime,
simulationMode,
} = this.props;
let heartbeatStatus = 'unknown';
let nLost = 0;
let timeDiff = null;
let timeDiffText = 'No heartbeat from producer.';

if (this.props.heartbeatData) {
nLost = this.props.heartbeatData.lost;
if (this.props.heartbeatData.last_heartbeat_timestamp === -1) timeDiff = -1;
else timeDiff = Math.ceil(props.serverTime.tai * 1000 - this.props.heartbeatData.last_heartbeat_timestamp);
heartbeatStatus = this.props.heartbeatData.lost > 0 || timeDiff < 0 ? 'alert' : 'ok';
if (heartbeatData) {
nLost = heartbeatData.lost;
if (heartbeatData.last_heartbeat_timestamp === -1) timeDiff = -1;
else timeDiff = Math.ceil(serverTime.tai * 1000 - heartbeatData.last_heartbeat_timestamp);
heartbeatStatus = heartbeatData.lost > 0 || timeDiff < 0 ? 'alert' : 'ok';
}
if (props.hasHeartbeat === false) {

if (!hasHeartbeat) {
heartbeatStatus = 'ok';
}

Expand All @@ -125,48 +155,56 @@ export default class CSCDetail extends Component {
timeDiffText = timeDiff < 0 ? 'Stale' : `${timeDiff} seconds ago`;
}

let title = `${cscText(this.props.name, this.props.salindex)} heartbeat\nLost: ${nLost}\n`;

let title = `${cscText(name, salindex)} heartbeat\nLost: ${nLost}\n`;
if (timeDiff === null) {
title += `${timeDiffText}`;
} else {
title += timeDiff < 0 ? `Last seen: ${timeDiffText}` : `${timeDiffText}`;
}
const summaryStateValue = this.props.summaryStateData ? this.props.summaryStateData.summaryState.value : 0;

const summaryStateValue = summaryStateData ? summaryStateData.summaryState.value : 0;
const summaryState = CSCDetail.states[summaryStateValue];
let stateClass = heartbeatStatus === 'alert' ? styles.alert : summaryState.class;
if (heartbeatStatus === 'unknown') stateClass = CSCDetail.states[0].class;
if (summaryState.name === 'UNKNOWN') stateClass = CSCDetail.states[0].class;

const summaryStateIsUnknown = summaryState === 'UNKNOWN';
let stateClass = summaryState.class;
if (heartbeatStatus === 'alert' && !summaryStateIsUnknown) {
stateClass = styles.alert;
}
if (heartbeatStatus === 'unknown' && !summaryStateIsUnknown) {
stateClass = styles.unknown;
}

const heartbeatIsOk = heartbeatStatus === 'ok';
const isSimulated = simulationMode?.mode.value > 0;
return (
<div
onClick={() => this.props.onCSCClick({ group: props.group, csc: props.name, salindex: props.salindex })}
className={[styles.CSCDetailContainer, this.props.embedded ? styles.minWidth : ''].join(' ')}
onClick={() => onCSCClick({ group: group, csc: name, salindex: salindex })}
className={[
styles.CSCDetailContainer,
embedded ? styles.minWidth : '',
isSimulated ? styles.simulated : '',
].join(' ')}
>
<div className={[styles.summaryStateSection, summaryState.class].join(' ')}>
<span className={styles.summaryState} title={summaryState.userReadable}>
{summaryState.char}
</span>
</div>
<div className={[styles.heartbeatSection, stateClass].join(' ')}>
<div
className={[
styles.heartbeatIconWrapper,
heartbeatStatus === 'ok' && props.hasHeartbeat !== false ? styles.hidden : '',
].join(' ')}
>
<HeartbeatIcon
status={heartbeatStatus === 'alert' || props.hasHeartbeat === false ? 'unknown' : heartbeatStatus}
title={title}
/>
<div className={[styles.heartbeatIconWrapper, !hasHeartbeat || heartbeatIsOk ? styles.hidden : ''].join(' ')}>
<HeartbeatIcon status={heartbeatStatus} title={title} />
</div>
</div>

<div className={[styles.nameSection, stateClass].join(' ')} title={this.props.name + '.' + this.props.salindex}>
{cscText(this.props.name, this.props.salindex)}
<div
className={[styles.nameSection, stateClass].join(' ')}
title={name + '.' + salindex + (isSimulated ? ' (SIMULATED)' : '')}
>
{cscText(name, salindex)}
</div>

<div className={[styles.warningIconSection, stateClass].join(' ')}>
<div className={[styles.warningIconWrapper, props.withWarning !== true ? styles.hidden : ''].join(' ')}>
<div className={[styles.warningIconWrapper, !withWarning ? styles.hidden : ''].join(' ')}>
<WarningIcon title="warning" />
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions love/src/components/CSCSummary/CSCDetail/CSCDetail.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,7 @@ this program. If not, see <http://www.gnu.org/licenses/>.
.hidden {
display: none;
}

.simulated {
border: 0.2em dashed var(--info-background-color);
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const CSCExpandedContainer = ({
subscribeToStreams,
unsubscribeToStreams,
heartbeatData,
simulationMode,
displaySummaryState = true,
hideTitle = false,
}) => {
Expand All @@ -107,6 +108,7 @@ const CSCExpandedContainer = ({
logMessageData={logMessageData}
heartbeatData={heartbeatData}
clearCSCLogMessages={clearCSCLogMessages}
simulationMode={simulationMode}
displaySummaryState={displaySummaryState}
hideTitle={hideTitle}
/>
Expand All @@ -124,6 +126,7 @@ const mapDispatchToProps = (dispatch) => {
dispatch(addGroup(`event-${cscName}-${index}-softwareVersions`));
dispatch(addGroup(`event-${cscName}-${index}-configurationsAvailable`));
dispatch(addGroup(`event-${cscName}-${index}-configurationApplied`));
dispatch(addGroup(`event-${cscName}-${index}-simulationMode`));
},
unsubscribeToStreams: (cscName, index) => {
dispatch(removeGroup('event-Heartbeat-0-stream'));
Expand All @@ -134,6 +137,7 @@ const mapDispatchToProps = (dispatch) => {
dispatch(removeGroup(`event-${cscName}-${index}-softwareVersions`));
dispatch(removeGroup(`event-${cscName}-${index}-configurationsAvailable`));
dispatch(removeGroup(`event-${cscName}-${index}-configurationApplied`));
dispatch(removeGroup(`event-${cscName}-${index}-simulationMode`));
},
clearCSCLogMessages: (csc, salindex) => {
dispatch(removeCSCLogMessages(csc, salindex));
Expand All @@ -152,29 +156,30 @@ const mapDispatchToProps = (dispatch) => {
};

const mapStateToProps = (state, ownProps) => {
let summaryStateData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-summaryState`);
let heartbeatData = getCSCHeartbeat(state, ownProps.name, ownProps.salindex);
let softwareVersions = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-softwareVersions`);
let configurationsAvailable = getStreamData(
const heartbeatData = getCSCHeartbeat(state, ownProps.name, ownProps.salindex);
const logMessageData = getCSCLogMessages(state, ownProps.name, ownProps.salindex);
const errorCodeData = getCSCErrorCodeData(state, ownProps.name, ownProps.salindex);

const summaryStateData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-summaryState`);
const softwareVersions = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-softwareVersions`);
const configurationsAvailable = getStreamData(
state,
`event-${ownProps.name}-${ownProps.salindex}-configurationsAvailable`,
);
let configurationApplied = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-configurationApplied`);
let cscLogLevelData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-logLevel`);

const logMessageData = getCSCLogMessages(state, ownProps.name, ownProps.salindex);
const errorCodeData = getCSCErrorCodeData(state, ownProps.name, ownProps.salindex);
summaryStateData = summaryStateData ? summaryStateData : {};
const configurationApplied = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-configurationApplied`);
const cscLogLevelData = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-logLevel`);
const simulationMode = getStreamData(state, `event-${ownProps.name}-${ownProps.salindex}-simulationMode`);

return {
summaryStateData: summaryStateData ? summaryStateData?.[0] : undefined,
softwareVersions: softwareVersions ? softwareVersions?.[0] : undefined,
configurationsAvailable: configurationsAvailable ? configurationsAvailable?.[0] : undefined,
configurationApplied: configurationApplied ? configurationApplied?.[0] : undefined,
cscLogLevelData: cscLogLevelData ? cscLogLevelData?.[0] : undefined,
heartbeatData,
logMessageData,
errorCodeData,
summaryStateData: summaryStateData?.[0],
softwareVersions: softwareVersions?.[0],
configurationsAvailable: configurationsAvailable?.[0],
configurationApplied: configurationApplied?.[0],
cscLogLevelData: cscLogLevelData?.[0],
simulationMode: simulationMode?.[0],
};
};

Expand Down
Loading

0 comments on commit eaac0d4

Please sign in to comment.