diff --git a/app/components/Block.test.tsx b/app/components/Block.test.tsx index 184896e..5dd11b4 100644 --- a/app/components/Block.test.tsx +++ b/app/components/Block.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import Block from "@/app/components/Block"; import { IfcBlock } from "@/app/types"; @@ -20,12 +20,7 @@ it("renders topbar unchanged", () => { }; const instName = "Instrument"; const { container } = render( - , + , { container: tableBody, }, @@ -36,12 +31,7 @@ it("renders topbar unchanged", () => { it("renders nothing if pv is hidden", () => { const aBlock: IfcBlock = { pvaddress: "SOME:PV", visible: false }; const { container } = render( - , + , { container: tableBody, }, @@ -56,12 +46,7 @@ it("renders block with correct name", () => { human_readable_name: "MyBlock", }; const { container } = render( - , + , { container: tableBody, }, @@ -80,12 +65,7 @@ it("renders block with run control that is in range as a tick", () => { runcontrol_enabled: true, }; const { container } = render( - , + , { container: tableBody, }, @@ -105,12 +85,7 @@ it("renders block with run control that is not in range as a cross", () => { runcontrol_enabled: true, }; const { container } = render( - , + , { container: tableBody, }, @@ -130,12 +105,7 @@ it("renders block without run control without tick or cross", () => { runcontrol_enabled: false, }; const { container } = render( - , + , { container: tableBody, }, @@ -159,19 +129,49 @@ it("renders block with SP and shows SP value", () => { value: expectedValue, }; const { container } = render( - , + , { container: tableBody, }, ); + const valueSpan = container.querySelector( + `#${aBlock.human_readable_name}_VALUE`, + )!; + + fireEvent.click(valueSpan); + expect(valueSpan.innerHTML).toContain(`${expectedValue}`); expect( - container.querySelector(`#${aBlock.human_readable_name}_VALUE`)!.innerHTML, - ).toContain(`${expectedValue}
(SP: ${expectedSpValue})`); + container.querySelector(`#${aBlock.human_readable_name}_SP`)!.innerHTML, + ).toContain(expectedSpValue.toString()); +}); + +it("renders block with timestamp and shows timestamp value", () => { + const expectedValue = 123; + const expectedTimeStamp = 1731342022; + const aBlock: IfcBlock = { + pvaddress: "SOME:PV", + visible: true, + human_readable_name: "MyBlock", + runcontrol_inrange: false, + runcontrol_enabled: false, + updateSeconds: expectedTimeStamp, + value: expectedValue, + }; + const { container } = render( + , + { + container: tableBody, + }, + ); + const valueSpan = container.querySelector( + `#${aBlock.human_readable_name}_VALUE`, + )!; + fireEvent.click(valueSpan); + expect(valueSpan.innerHTML).toContain(`${expectedValue}`); + expect( + container.querySelector(`#${aBlock.human_readable_name}_TIMESTAMP`)! + .innerHTML, + ).toContain(new Date(expectedTimeStamp * 1000).toLocaleString()); }); it("renders block without SP and hides SP value", () => { @@ -187,12 +187,7 @@ it("renders block without SP and hides SP value", () => { sp_value: expectedSpValue, }; const { container } = render( - , + , { container: tableBody, }, diff --git a/app/components/Block.tsx b/app/components/Block.tsx index e679656..15c3d48 100644 --- a/app/components/Block.tsx +++ b/app/components/Block.tsx @@ -1,6 +1,6 @@ "use client"; import { IfcBlock } from "@/app/types"; -import { useState } from "react"; +import React, { useState } from "react"; const grafana_stub = "https://shadow.nd.rl.ac.uk/grafana/d/wMlwwaHMk/block-history?viewPanel=2&orgId=1&var-block="; @@ -9,16 +9,15 @@ export default function Block({ pv, instName, showHiddenBlocks, - showSetpoints, }: { pv: IfcBlock; instName: string; showHiddenBlocks: boolean; - showSetpoints: boolean; }) { const [currentValue, setCurrentValue] = useState< string | number | undefined >(); + const [showAdvanced, setShowAdvanced] = useState(false); if (!pv.visible && !showHiddenBlocks && !instName) { return null; } @@ -38,12 +37,16 @@ export default function Block({ }, 2000); } + const minimum_date_to_be_shown = 631152000; // This is what PVWS thinks epoch time is for some reason. don't bother showing it as the instrument wasn't running EPICS on 01/01/1990 return ( { + setShowAdvanced(!showAdvanced); + }} > - + - - - {pv.value} {pv.units != null && pv.units} - {showSetpoints && pv.sp_value != null ? ( - <> -
- {`(SP: ${pv.sp_value})`} - - ) : null} - {pv.severity != "NONE" ? ( -
+ +
+ -
- {pv.severity} -
- ) : null} + {showAdvanced && "Readback: "} + {pv.value} {pv.units != null && pv.units} + + + + +
+ + {showAdvanced && ( +
+
+ {pv.severity != "NONE" ? ( + + Alarm: {pv.severity} + + ) : null} +
+ {pv.sp_value != null ? ( + + {`Setpoint: ${pv.sp_value}`} +
+
+ ) : null} + {pv.updateSeconds != null && + pv.updateSeconds > minimum_date_to_be_shown ? ( + + {/*Multiply by 1000 here as Date() expects milliseconds*/} + {`Last update: ${new Date(pv.updateSeconds * 1000).toLocaleString()}`} + + ) : null} +
+ )} - - + + {pv.runcontrol_enabled && (pv.runcontrol_inrange ? "✅" : "❌")} - - - + {showAdvanced ? "-" : "+"} + ); diff --git a/app/components/Group.tsx b/app/components/Group.tsx index a6dbe83..d9837a6 100644 --- a/app/components/Group.tsx +++ b/app/components/Group.tsx @@ -8,12 +8,10 @@ export default function Group({ group, instName, showHiddenBlocks, - showSetpoints, }: { group: IfcGroup; instName: string; showHiddenBlocks: boolean; - showSetpoints: boolean; }) { if (!group) { return

Loading...

; @@ -30,14 +28,14 @@ export default function Group({ {group.name} - - - - - + + + + + - + {group.blocks.map((pv) => { return ( ); })} diff --git a/app/components/Groups.tsx b/app/components/Groups.tsx index ef61b70..389dc22 100644 --- a/app/components/Groups.tsx +++ b/app/components/Groups.tsx @@ -7,19 +7,17 @@ export default function Groups({ groupsMap, instName, showHiddenBlocks, - showSetpoints, }: { groupsMap: Array; instName: string; showHiddenBlocks: boolean; - showSetpoints: boolean; }) { if (!groupsMap) { return

Loading...

; } return ( -
+
{groupsMap.map((group) => { return ( ); })} diff --git a/app/components/InstrumentPage.tsx b/app/components/InstrumentPage.tsx index 16198a3..c5c67dd 100644 --- a/app/components/InstrumentPage.tsx +++ b/app/components/InstrumentPage.tsx @@ -99,6 +99,7 @@ export function toPrecision( function InstrumentData({ instrumentName }: { instrumentName: string }) { const [showHiddenBlocks, setShowHiddenBlocks] = useState(false); const [showSetpoints, setShowSetpoints] = useState(false); + const [showTimestamps, setShowTimestamps] = useState(false); const CONFIG_DETAILS = "CS:BLOCKSERVER:GET_CURR_CONFIG_DETAILS"; const [instlist, setInstlist] = useState | null>(null); const [currentInstrument, setCurrentInstrument] = useState( @@ -242,6 +243,7 @@ function InstrumentData({ instrumentName }: { instrumentName: string }) { } // if a block has precision truncate it here block.value = toPrecision(block, pvVal); + if (updatedPV.seconds) block.updateSeconds = updatedPV.seconds; if (updatedPV.units) block.units = updatedPV.units; if (updatedPV.severity) block.severity = updatedPV.severity; @@ -262,29 +264,23 @@ function InstrumentData({ instrumentName }: { instrumentName: string }) { return

Loading...

; } return ( -
+
-
+
); diff --git a/app/components/TopBar.test.ts b/app/components/TopBar.test.ts index 2a8fad0..962be43 100644 --- a/app/components/TopBar.test.ts +++ b/app/components/TopBar.test.ts @@ -47,7 +47,7 @@ it("draws instName expectedly", () => { runInfoPVs: instrument.runInfoPVs, }), ); - expect(container.querySelector("#instNameSpan")!.innerHTML).toBe( + expect(container.querySelector("#instNameLabel")!.innerHTML).toContain( instName.toUpperCase(), ); }); diff --git a/app/components/TopBar.tsx b/app/components/TopBar.tsx index 458b051..07b1450 100644 --- a/app/components/TopBar.tsx +++ b/app/components/TopBar.tsx @@ -42,22 +42,6 @@ export default function TopBar({ id="top_bar" className="w-full bg-white shadow-lg text-black rounded-xl text-md" > -
-

- Instrument:{" "} - - {instName.toUpperCase()} - -

-

- Config:{" "} - - {findPVByHumanReadableName(runInfoPVs, configName) - ? findPVByHumanReadableName(runInfoPVs, configName)!.value - : "UNKNOWN"} - -

-
{instName.toUpperCase()} is{" "} {getRunstate(runInfoPVs)} -
+

+ Config:{" "} + + {findPVByHumanReadableName(runInfoPVs, configName) + ? findPVByHumanReadableName(runInfoPVs, configName)!.value + : "UNKNOWN"} + +

+ +
BlockValueIn-Range
BlockValue
{dashboard.map((column: Array>, index: number) => ( - + {column.map((row: Array, index: number) => ( - - +
{row[0].value} + + {row[0].value} + {row[1].value != null ? row[1].value : "Hidden/unknown"} @@ -98,17 +94,23 @@ export default function TopBar({
-