From 9cb8f02b5b9338638d6e41c5372204d50dcd07a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Thu, 21 Sep 2023 13:45:59 +0200 Subject: [PATCH 1/7] Move alerts to header component --- frontend/src/App.tsx | 39 ++++--- .../src/components/Alerts/AlertsBanner.tsx | 44 ++++++++ .../components/Alerts/FailedMissionAlert.tsx | 60 ++++++++++ .../src/components/Contexts/AlertContext.tsx | 104 ++++++++++++++++++ frontend/src/components/Header/Header.tsx | 75 +++++++------ .../components/Pages/FrontPage/FrontPage.tsx | 2 - 6 files changed, 271 insertions(+), 53 deletions(-) create mode 100644 frontend/src/components/Alerts/AlertsBanner.tsx create mode 100644 frontend/src/components/Alerts/FailedMissionAlert.tsx create mode 100644 frontend/src/components/Contexts/AlertContext.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b2c76953a..3825dcb31 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,28 +6,31 @@ import { MissionControlProvider } from 'components/Contexts/MissionControlContex import { MissionFilterProvider } from 'components/Contexts/MissionFilterContext' import { MissionsProvider } from 'components/Contexts/MissionListsContext' import { SafeZoneProvider } from 'components/Contexts/SafeZoneContext' +import { AlertProvider } from 'components/Contexts/AlertContext' function App() { return ( - - - - <> - -
- -
-
- - - - - - -
-
-
+ + + + + <> + +
+ +
+
+ + + + + + +
+
+
+
) } diff --git a/frontend/src/components/Alerts/AlertsBanner.tsx b/frontend/src/components/Alerts/AlertsBanner.tsx new file mode 100644 index 000000000..a71402255 --- /dev/null +++ b/frontend/src/components/Alerts/AlertsBanner.tsx @@ -0,0 +1,44 @@ +import { Button, Card, Icon } from '@equinor/eds-core-react' +import { tokens } from '@equinor/eds-tokens' +import { ReactNode } from 'react' +import styled from 'styled-components' +import { Icons } from 'utils/icons' + +const StyledCard = styled(Card)` + display: flex; + width: 100%; + padding: 7px 15px; + gap: 0.2rem; +` + +const Horizontal = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; +` + +const Center = styled.div` + align-items: center; +` + +interface AlertProps { + children: ReactNode + dismissAlert: () => void +} + +export function AlertBanner({ children, dismissAlert }: AlertProps) { + return ( + <> + + +
+ {children} +
+ +
+
+ + ) +} diff --git a/frontend/src/components/Alerts/FailedMissionAlert.tsx b/frontend/src/components/Alerts/FailedMissionAlert.tsx new file mode 100644 index 000000000..48be4fb6f --- /dev/null +++ b/frontend/src/components/Alerts/FailedMissionAlert.tsx @@ -0,0 +1,60 @@ +import { Button, Typography } from '@equinor/eds-core-react' +import { config } from 'config' +import { Mission, MissionStatus } from 'models/Mission' +import styled from 'styled-components' +import { MissionStatusDisplay } from '../Pages/FrontPage/MissionOverview/MissionStatusDisplay' +import { useNavigate } from 'react-router-dom' +import { useLanguageContext } from 'components/Contexts/LanguageContext' + +const Indent = styled.div` + padding: 0px 9px; +` + +interface MissionsProps { + missions: Mission[] +} + +function FailedMission({ missions }: MissionsProps) { + const mission = missions[0] + const { TranslateText } = useLanguageContext() + const navigate = useNavigate() + const goToMission = () => { + const path = `${config.FRONTEND_BASE_ROUTE}/mission/${mission.id}` + navigate(path) + } + + return ( + + ) +} + +function SeveralFailedMissions({ missions }: MissionsProps) { + const { TranslateText } = useLanguageContext() + const navigate = useNavigate() + const goToHistory = () => { + const path = `${config.FRONTEND_BASE_ROUTE}/history` + navigate(path) + } + + return ( + + ) +} + +export function FailedMissionAlertContent({ missions }: MissionsProps) { + return ( + <> + + + {missions.length === 1 && } + {missions.length > 1 && } + + + ) +} diff --git a/frontend/src/components/Contexts/AlertContext.tsx b/frontend/src/components/Contexts/AlertContext.tsx new file mode 100644 index 000000000..b3adff7be --- /dev/null +++ b/frontend/src/components/Contexts/AlertContext.tsx @@ -0,0 +1,104 @@ +import { createContext, FC, ReactNode, useContext, useEffect, useState } from 'react' +import { addMinutes, max } from 'date-fns' +import { MissionStatus } from 'models/Mission' +import { FailedMissionAlertContent } from 'components/Alerts/FailedMissionAlert' +import { BackendAPICaller } from 'api/ApiCaller' +import { refreshInterval } from 'components/Pages/FrontPage/FrontPage' + +export enum AlertSource { + MissionFail, + RequestFail +} + +type AlertDictionaryType = { [key in AlertSource]?: {content: ReactNode | undefined, dismissFunction: () => void} } + +interface IAlertContext { + alerts: AlertDictionaryType + setAlert: (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => void + cleartAlerts: () => void + clearAlert: (source: AlertSource) => void +} + +interface Props { + children: React.ReactNode +} + +const defaultAlertInterface = { + alerts: {}, + setAlert: (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => {}, + cleartAlerts: () => {}, + clearAlert: (source: AlertSource) => {} +} + +export const AlertContext = createContext(defaultAlertInterface) + +export const AlertProvider: FC = ({ children }) => { + const [alerts, setAlerts] = useState(defaultAlertInterface.alerts) + + const setAlert = (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => { + let newAlerts = { ...alerts } + newAlerts[source] = {content: alert, dismissFunction: dismissFunction} + setAlerts(newAlerts) + } + + const cleartAlerts = () => { + setAlerts({}) + } + + const clearAlert = (source: AlertSource) => { + let newAlerts = { ...alerts } + delete newAlerts[source] + setAlerts(newAlerts) + } + + // Here we update the recent failed missions + const dismissMissionFailTimeKey: string = 'lastMissionFailDismissalTime' + + const dismissCurrentMissions = () => { + sessionStorage.setItem(dismissMissionFailTimeKey, JSON.stringify(Date.now())) + clearAlert(AlertSource.MissionFail) + } + + useEffect(() => { + const pageSize: number = 100 + // The default amount of minutes in the past for failed missions to generate an alert + const defaultTimeInterval: number = 10 + // The maximum amount of minutes in the past for failed missions to generate an alert + const maxTimeInterval: number = 60 + + const getLastDismissalTime = (): Date => { + const sessionValue = sessionStorage.getItem(dismissMissionFailTimeKey) + if (sessionValue === null || sessionValue === '') { + return addMinutes(Date.now(), -defaultTimeInterval) + } else { + // If last dismissal time was more than {MaxTimeInterval} minutes ago, use the limit value instead + return max([addMinutes(Date.now(), -maxTimeInterval), JSON.parse(sessionValue)]) + } + } + + const id = setInterval(() => { + const lastDismissTime: Date = getLastDismissalTime() + BackendAPICaller.getMissionRuns({ statuses: [MissionStatus.Failed], pageSize: pageSize }).then((missions) => { + const newRecentFailedMissions = missions.content.filter((m) => new Date(m.endTime!) > lastDismissTime) + if (newRecentFailedMissions.length > 0) + setAlert(AlertSource.MissionFail, , dismissCurrentMissions) + }) + }, refreshInterval) + return () => clearInterval(id) + }, []) + + return ( + + {children} + + ) +} + +export const useAlertContext = () => useContext(AlertContext) diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index 152b71cfd..7e74b28ac 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -4,6 +4,8 @@ import { useInstallationContext } from 'components/Contexts/InstallationContext' import styled from 'styled-components' import { SelectLanguage } from './LanguageSelector' import { Icons } from 'utils/icons' +import { useAlertContext } from 'components/Contexts/AlertContext' +import { AlertBanner } from 'components/Alerts/AlertsBanner' const StyledTopBar = styled(TopBar)` margin-bottom: 2rem; @@ -29,43 +31,50 @@ const SelectLanguageWrapper = styled.div` ` export function Header({ page }: { page: string }) { + const { alerts } = useAlertContext() const { installationName } = useInstallationContext() return ( - - - { - window.location.href = `${config.FRONTEND_BASE_ROUTE}/FrontPage` - }} - > - - Flotilla - - - - {installationName} - - - - - - - - {SelectLanguage()} - - + + Flotilla + + + + {installationName} + + + + + + + + {SelectLanguage()} + + + {Object.entries(alerts).length > 0 && + Object.entries(alerts).map(([key, value]) => ( + {value.content} + ))} + ) } diff --git a/frontend/src/components/Pages/FrontPage/FrontPage.tsx b/frontend/src/components/Pages/FrontPage/FrontPage.tsx index be29c2da8..14344cab7 100644 --- a/frontend/src/components/Pages/FrontPage/FrontPage.tsx +++ b/frontend/src/components/Pages/FrontPage/FrontPage.tsx @@ -1,7 +1,6 @@ import { MissionQueueView } from 'components/Pages/FrontPage/MissionOverview/MissionQueueView' import { OngoingMissionView } from 'components/Pages/FrontPage/MissionOverview/OngoingMissionView' import { RobotStatusSection } from 'components/Pages/FrontPage/RobotCards/RobotStatusView' -import { FailedMissionAlertView } from './MissionOverview/FailedMissionAlertView' import { Header } from 'components/Header/Header' import styled from 'styled-components' import { InspectionOverviewSection } from '../InspectionPage/InspectionOverview' @@ -33,7 +32,6 @@ export function FrontPage() { <>
- From ff5becbbbe80459453119edf9934977adc565900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Thu, 21 Sep 2023 14:39:26 +0200 Subject: [PATCH 2/7] Show alert for failing to get echo missions --- frontend/src/api/ApiCaller.tsx | 11 +-- .../src/components/Alerts/AlertsBanner.tsx | 4 +- .../components/Alerts/FailedRequestAlert.tsx | 37 ++++++++++ .../src/components/Contexts/AlertContext.tsx | 72 ++++++++++--------- frontend/src/components/Header/Header.tsx | 20 ++++-- .../MissionOverview/MissionQueueView.tsx | 21 ++++-- frontend/src/language/en.json | 4 +- frontend/src/language/no.json | 4 +- 8 files changed, 116 insertions(+), 57 deletions(-) create mode 100644 frontend/src/components/Alerts/FailedRequestAlert.tsx diff --git a/frontend/src/api/ApiCaller.tsx b/frontend/src/api/ApiCaller.tsx index 81c74966e..a11efd633 100644 --- a/frontend/src/api/ApiCaller.tsx +++ b/frontend/src/api/ApiCaller.tsx @@ -183,7 +183,7 @@ export class BackendAPICaller { return { pagination: pagination, content: result.content } } - static async getAvailableEchoMission(installationCode: string = ''): Promise { + static async getAvailableEchoMissions(installationCode: string = ''): Promise { const path: string = 'echo/available-missions/' + installationCode const result = await BackendAPICaller.GET(path).catch((e) => { console.error(`Failed to GET /${path}: ` + e) @@ -263,15 +263,6 @@ export class BackendAPICaller { }) } - static async getEchoMissions(installationCode: string = ''): Promise { - const path: string = 'echo/missions?installationCode=' + installationCode - const result = await BackendAPICaller.GET(path).catch((e) => { - console.error(`Failed to GET /${path}: ` + e) - throw e - }) - return result.content - } - static async getMissionDefinitionById(missionId: string): Promise { const path: string = 'missions/definitions/' + missionId + '/condensed' const result = await BackendAPICaller.GET(path).catch((e) => { diff --git a/frontend/src/components/Alerts/AlertsBanner.tsx b/frontend/src/components/Alerts/AlertsBanner.tsx index a71402255..6f5517b51 100644 --- a/frontend/src/components/Alerts/AlertsBanner.tsx +++ b/frontend/src/components/Alerts/AlertsBanner.tsx @@ -31,9 +31,7 @@ export function AlertBanner({ children, dismissAlert }: AlertProps) { <> -
- {children} -
+
{children}
diff --git a/frontend/src/components/Alerts/FailedRequestAlert.tsx b/frontend/src/components/Alerts/FailedRequestAlert.tsx new file mode 100644 index 000000000..281a008bb --- /dev/null +++ b/frontend/src/components/Alerts/FailedRequestAlert.tsx @@ -0,0 +1,37 @@ +import { Icon, Typography } from '@equinor/eds-core-react' +import styled from 'styled-components' +import { useLanguageContext } from 'components/Contexts/LanguageContext' +import { Icons } from 'utils/icons' +import { tokens } from '@equinor/eds-tokens' + +const StyledDiv = styled.div` + align-items: center; + > * { + margin-left: 1rem; + } +` + +const Indent = styled.div` + padding: 0px 30px; +` + +const StyledAlertTitle = styled.div` + display: flex; + gap: 0.3em; + align-items: flex-end; +` + +export function FailedRequestAlertContent({ message }: { message: string }) { + const { TranslateText } = useLanguageContext() + return ( + + + + {TranslateText('Request error')} + + + {TranslateText(message)} + + + ) +} diff --git a/frontend/src/components/Contexts/AlertContext.tsx b/frontend/src/components/Contexts/AlertContext.tsx index b3adff7be..31a0197bc 100644 --- a/frontend/src/components/Contexts/AlertContext.tsx +++ b/frontend/src/components/Contexts/AlertContext.tsx @@ -1,22 +1,22 @@ import { createContext, FC, ReactNode, useContext, useEffect, useState } from 'react' import { addMinutes, max } from 'date-fns' -import { MissionStatus } from 'models/Mission' +import { Mission, MissionStatus } from 'models/Mission' import { FailedMissionAlertContent } from 'components/Alerts/FailedMissionAlert' import { BackendAPICaller } from 'api/ApiCaller' import { refreshInterval } from 'components/Pages/FrontPage/FrontPage' -export enum AlertSource { +export enum AlertType { MissionFail, - RequestFail + RequestFail, } -type AlertDictionaryType = { [key in AlertSource]?: {content: ReactNode | undefined, dismissFunction: () => void} } +type AlertDictionaryType = { [key in AlertType]?: { content: ReactNode | undefined; dismissFunction: () => void } } interface IAlertContext { alerts: AlertDictionaryType - setAlert: (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => void - cleartAlerts: () => void - clearAlert: (source: AlertSource) => void + setAlert: (source: AlertType, alert: ReactNode) => void + clearAlerts: () => void + clearAlert: (source: AlertType) => void } interface Props { @@ -25,9 +25,9 @@ interface Props { const defaultAlertInterface = { alerts: {}, - setAlert: (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => {}, - cleartAlerts: () => {}, - clearAlert: (source: AlertSource) => {} + setAlert: (source: AlertType, alert: ReactNode) => {}, + clearAlerts: () => {}, + clearAlert: (source: AlertType) => {}, } export const AlertContext = createContext(defaultAlertInterface) @@ -35,30 +35,25 @@ export const AlertContext = createContext(defaultAlertInterface) export const AlertProvider: FC = ({ children }) => { const [alerts, setAlerts] = useState(defaultAlertInterface.alerts) - const setAlert = (source: AlertSource, alert: ReactNode, dismissFunction: () => void) => { - let newAlerts = { ...alerts } - newAlerts[source] = {content: alert, dismissFunction: dismissFunction} - setAlerts(newAlerts) - } + const dismissMissionFailTimeKey: string = 'lastMissionFailDismissalTime' - const cleartAlerts = () => { - setAlerts({}) - } + const setAlert = (source: AlertType, alert: ReactNode) => + setAlerts({ ...alerts, [source]: { content: alert, dismissFunction: () => clearAlert(source) } }) + + const clearAlerts = () => setAlerts({}) - const clearAlert = (source: AlertSource) => { + const clearAlert = (source: AlertType) => { + if (source === AlertType.MissionFail) + sessionStorage.setItem(dismissMissionFailTimeKey, JSON.stringify(Date.now())) let newAlerts = { ...alerts } delete newAlerts[source] setAlerts(newAlerts) } - // Here we update the recent failed missions - const dismissMissionFailTimeKey: string = 'lastMissionFailDismissalTime' - - const dismissCurrentMissions = () => { - sessionStorage.setItem(dismissMissionFailTimeKey, JSON.stringify(Date.now())) - clearAlert(AlertSource.MissionFail) - } + // This variable is needed since the state in the useEffect below uses an outdated alert object + const [newFailedMissions, setNewFailedMissions] = useState([]) + // Here we update the recent failed missions useEffect(() => { const pageSize: number = 100 // The default amount of minutes in the past for failed missions to generate an alert @@ -78,22 +73,33 @@ export const AlertProvider: FC = ({ children }) => { const id = setInterval(() => { const lastDismissTime: Date = getLastDismissalTime() - BackendAPICaller.getMissionRuns({ statuses: [MissionStatus.Failed], pageSize: pageSize }).then((missions) => { - const newRecentFailedMissions = missions.content.filter((m) => new Date(m.endTime!) > lastDismissTime) - if (newRecentFailedMissions.length > 0) - setAlert(AlertSource.MissionFail, , dismissCurrentMissions) - }) + BackendAPICaller.getMissionRuns({ statuses: [MissionStatus.Failed], pageSize: pageSize }).then( + (missions) => { + const newRecentFailedMissions = missions.content.filter( + (m) => new Date(m.endTime!) > lastDismissTime + ) + if (newRecentFailedMissions.length > 0) setNewFailedMissions(newRecentFailedMissions) + } + ) }, refreshInterval) return () => clearInterval(id) }, []) + useEffect(() => { + if (newFailedMissions.length > 0) { + setAlert(AlertType.MissionFail, ) + setNewFailedMissions([]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [newFailedMissions]) + return ( {children} diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index 7e74b28ac..f0b18c1db 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -30,6 +30,13 @@ const SelectLanguageWrapper = styled.div` margin-left: 1.5rem; ` +const StyledAlertList = styled.div` + display: grid; + grid-template-rows: repeat(auto-fill); + align-items: center; + gap: 0.5rem; +` + export function Header({ page }: { page: string }) { const { alerts } = useAlertContext() const { installationName } = useInstallationContext() @@ -71,10 +78,15 @@ export function Header({ page }: { page: string }) { {SelectLanguage()} - {Object.entries(alerts).length > 0 && - Object.entries(alerts).map(([key, value]) => ( - {value.content} - ))} + {Object.entries(alerts).length > 0 && ( + + {Object.entries(alerts).map(([key, value]) => ( + + {value.content} + + ))} + + )} ) } diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx index 921264ba7..b915fc13b 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx @@ -13,6 +13,8 @@ import { CreateMissionButton } from './CreateMissionButton' import { EchoMissionDefinition } from 'models/MissionDefinition' import { useMissionsContext } from 'components/Contexts/MissionListsContext' import { useRobotContext } from 'components/Contexts/RobotContext' +import { AlertType, useAlertContext } from 'components/Contexts/AlertContext' +import { FailedRequestAlertContent } from 'components/Alerts/FailedRequestAlert' const StyledMissionView = styled.div` display: grid; @@ -43,6 +45,7 @@ export function MissionQueueView() { const { TranslateText } = useLanguageContext() const { missionQueue, ongoingMissions } = useMissionsContext() const { installationCode } = useInstallationContext() + const { setAlert } = useAlertContext() const [loadingMissionNames, setLoadingMissionNames] = useState>(new Set()) const [selectedEchoMissions, setSelectedEchoMissions] = useState([]) @@ -57,11 +60,19 @@ export function MissionQueueView() { const fetchEchoMissions = () => { setIsFetchingEchoMissions(true) - BackendAPICaller.getAvailableEchoMission(installationCode as string).then((missions) => { - const echoMissionsMap: Map = mapEchoMissionToString(missions) - setEchoMissions(echoMissionsMap) - setIsFetchingEchoMissions(false) - }) + BackendAPICaller.getAvailableEchoMissions(installationCode as string) + .then((missions) => { + const echoMissionsMap: Map = mapEchoMissionToString(missions) + setEchoMissions(echoMissionsMap) + setIsFetchingEchoMissions(false) + }) + .catch((_) => { + setAlert( + AlertType.RequestFail, + + ) + setIsFetchingEchoMissions(false) + }) } const onChangeMissionSelections = (selectedEchoMissions: string[]) => { diff --git a/frontend/src/language/en.json b/frontend/src/language/en.json index 2d03fbdab..c9befd932 100644 --- a/frontend/src/language/en.json +++ b/frontend/src/language/en.json @@ -202,5 +202,7 @@ "Continue missions": "Continue missions", "Safe Zone": "Safe Zone", "Edit mission definition": "Edit mission definition", - "Completed Tasks": "Completed Tasks" + "Completed Tasks": "Completed Tasks", + "Confirm plant": "Confirm plant", + "Failed to retrieve echo missions": "Failed to retrieve echo missions" } diff --git a/frontend/src/language/no.json b/frontend/src/language/no.json index 7b0d7a634..a5746f2ff 100644 --- a/frontend/src/language/no.json +++ b/frontend/src/language/no.json @@ -202,5 +202,7 @@ "Continue missions": "Fortsett oppdragene", "Safe Zone": "Trygg sone", "Edit mission definition": "Rediger oppdragsdefinisjon", - "Completed Tasks": "Fullførte oppgaver" + "Completed Tasks": "Fullførte oppgaver", + "Confirm plant": "Bekreft anlegg", + "Failed to retrieve echo missions": "Kunne ikke hente ut oppdrag fra echo" } From 2c62be7963f2023d9b1219306b908348d5c2704c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Mon, 25 Sep 2023 13:26:00 +0200 Subject: [PATCH 3/7] Reduce dependency between mission view and dialog --- .../Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx index 752d02782..2d40aace7 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx @@ -230,4 +230,4 @@ export const ScheduleMissionDialog = (props: IProps): JSX.Element => { ) -} +} \ No newline at end of file From e9900cff50624d42f7dbc70fffacc6a26d9b8378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Wed, 11 Oct 2023 14:14:54 +0200 Subject: [PATCH 4/7] Make banner styles more similar --- .../components/Alerts/FailedRequestAlert.tsx | 17 ++++++++--------- .../MissionOverview/MissionQueueView.tsx | 5 +++-- frontend/src/language/en.json | 3 ++- frontend/src/language/no.json | 3 ++- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/Alerts/FailedRequestAlert.tsx b/frontend/src/components/Alerts/FailedRequestAlert.tsx index 281a008bb..b98c39ee3 100644 --- a/frontend/src/components/Alerts/FailedRequestAlert.tsx +++ b/frontend/src/components/Alerts/FailedRequestAlert.tsx @@ -1,4 +1,4 @@ -import { Icon, Typography } from '@equinor/eds-core-react' +import { Button, Icon, Typography } from '@equinor/eds-core-react' import styled from 'styled-components' import { useLanguageContext } from 'components/Contexts/LanguageContext' import { Icons } from 'utils/icons' @@ -6,13 +6,6 @@ import { tokens } from '@equinor/eds-tokens' const StyledDiv = styled.div` align-items: center; - > * { - margin-left: 1rem; - } -` - -const Indent = styled.div` - padding: 0px 30px; ` const StyledAlertTitle = styled.div` @@ -21,6 +14,10 @@ const StyledAlertTitle = styled.div` align-items: flex-end; ` +const Indent = styled.div` + padding: 0px 9px; +` + export function FailedRequestAlertContent({ message }: { message: string }) { const { TranslateText } = useLanguageContext() return ( @@ -30,7 +27,9 @@ export function FailedRequestAlertContent({ message }: { message: string }) { {TranslateText('Request error')} - {TranslateText(message)} + ) diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx index b915fc13b..4cb01f1d5 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/MissionQueueView.tsx @@ -44,8 +44,10 @@ const mapEchoMissionToString = (missions: EchoMissionDefinition[]): Map([]) + const { enabledRobots } = useRobotContext() const [loadingMissionNames, setLoadingMissionNames] = useState>(new Set()) const [selectedEchoMissions, setSelectedEchoMissions] = useState([]) @@ -53,7 +55,6 @@ export function MissionQueueView() { const [echoMissions, setEchoMissions] = useState>( new Map() ) - const { enabledRobots } = useRobotContext() const [scheduleButtonDisabled, setScheduleButtonDisabled] = useState(true) const [frontPageScheduleButtonDisabled, setFrontPageScheduleButtonDisabled] = useState(true) const [isFetchingEchoMissions, setIsFetchingEchoMissions] = useState(false) diff --git a/frontend/src/language/en.json b/frontend/src/language/en.json index c9befd932..a75a8ee0c 100644 --- a/frontend/src/language/en.json +++ b/frontend/src/language/en.json @@ -204,5 +204,6 @@ "Edit mission definition": "Edit mission definition", "Completed Tasks": "Completed Tasks", "Confirm plant": "Confirm plant", - "Failed to retrieve echo missions": "Failed to retrieve echo missions" + "Failed to retrieve echo missions": "Failed to retrieve echo missions", + "Request error": "Request error" } diff --git a/frontend/src/language/no.json b/frontend/src/language/no.json index a5746f2ff..454f67e7b 100644 --- a/frontend/src/language/no.json +++ b/frontend/src/language/no.json @@ -204,5 +204,6 @@ "Edit mission definition": "Rediger oppdragsdefinisjon", "Completed Tasks": "Fullførte oppgaver", "Confirm plant": "Bekreft anlegg", - "Failed to retrieve echo missions": "Kunne ikke hente ut oppdrag fra echo" + "Failed to retrieve echo missions": "Kunne ikke hente ut oppdrag fra echo", + "Request error": "Forespørselsfeil" } From 3884f6643d6c63087dab06f88eaa44d35890fac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Tue, 7 Nov 2023 13:08:25 +0100 Subject: [PATCH 5/7] Align items in alert banner --- frontend/src/components/Alerts/FailedMissionAlert.tsx | 10 ++++------ .../MissionOverview/ScheduleMissionDialog.tsx | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Alerts/FailedMissionAlert.tsx b/frontend/src/components/Alerts/FailedMissionAlert.tsx index 48be4fb6f..856cc6b83 100644 --- a/frontend/src/components/Alerts/FailedMissionAlert.tsx +++ b/frontend/src/components/Alerts/FailedMissionAlert.tsx @@ -49,12 +49,10 @@ function SeveralFailedMissions({ missions }: MissionsProps) { export function FailedMissionAlertContent({ missions }: MissionsProps) { return ( - <> + - - {missions.length === 1 && } - {missions.length > 1 && } - - + {missions.length === 1 && } + {missions.length > 1 && } + ) } diff --git a/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx b/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx index 2d40aace7..752d02782 100644 --- a/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx +++ b/frontend/src/components/Pages/FrontPage/MissionOverview/ScheduleMissionDialog.tsx @@ -230,4 +230,4 @@ export const ScheduleMissionDialog = (props: IProps): JSX.Element => { ) -} \ No newline at end of file +} From 3b920bc441461742468289b5dcd7ecdea1d8a810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Thu, 9 Nov 2023 14:37:26 +0100 Subject: [PATCH 6/7] Readd signalR to alert banner --- .../src/components/Contexts/AlertContext.tsx | 79 +++++++++++++------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/Contexts/AlertContext.tsx b/frontend/src/components/Contexts/AlertContext.tsx index 31a0197bc..a1cda60c2 100644 --- a/frontend/src/components/Contexts/AlertContext.tsx +++ b/frontend/src/components/Contexts/AlertContext.tsx @@ -3,7 +3,8 @@ import { addMinutes, max } from 'date-fns' import { Mission, MissionStatus } from 'models/Mission' import { FailedMissionAlertContent } from 'components/Alerts/FailedMissionAlert' import { BackendAPICaller } from 'api/ApiCaller' -import { refreshInterval } from 'components/Pages/FrontPage/FrontPage' +import { SignalREventLabels, useSignalRContext } from './SignalRContext' +import { useInstallationContext } from './InstallationContext' export enum AlertType { MissionFail, @@ -34,7 +35,15 @@ export const AlertContext = createContext(defaultAlertInterface) export const AlertProvider: FC = ({ children }) => { const [alerts, setAlerts] = useState(defaultAlertInterface.alerts) - + const [recentFailedMissions, setRecentFailedMissions] = useState([]) + const { registerEvent, connectionReady } = useSignalRContext() + const { installationCode } = useInstallationContext() + + const pageSize: number = 100 + // The default amount of minutes in the past for failed missions to generate an alert + const defaultTimeInterval: number = 10 + // The maximum amount of minutes in the past for failed missions to generate an alert + const maxTimeInterval: number = 60 const dismissMissionFailTimeKey: string = 'lastMissionFailDismissalTime' const setAlert = (source: AlertType, alert: ReactNode) => @@ -50,40 +59,62 @@ export const AlertProvider: FC = ({ children }) => { setAlerts(newAlerts) } + const getLastDismissalTime = (): Date => { + const sessionValue = sessionStorage.getItem(dismissMissionFailTimeKey) + if (sessionValue === null || sessionValue === '') { + return addMinutes(Date.now(), -defaultTimeInterval) + } else { + // If last dismissal time was more than {MaxTimeInterval} minutes ago, use the limit value instead + return max([addMinutes(Date.now(), -maxTimeInterval), JSON.parse(sessionValue)]) + } + } + // This variable is needed since the state in the useEffect below uses an outdated alert object const [newFailedMissions, setNewFailedMissions] = useState([]) - // Here we update the recent failed missions + // Set the initial failed missions when loading the page or changing installations useEffect(() => { - const pageSize: number = 100 - // The default amount of minutes in the past for failed missions to generate an alert - const defaultTimeInterval: number = 10 - // The maximum amount of minutes in the past for failed missions to generate an alert - const maxTimeInterval: number = 60 - - const getLastDismissalTime = (): Date => { - const sessionValue = sessionStorage.getItem(dismissMissionFailTimeKey) - if (sessionValue === null || sessionValue === '') { - return addMinutes(Date.now(), -defaultTimeInterval) - } else { - // If last dismissal time was more than {MaxTimeInterval} minutes ago, use the limit value instead - return max([addMinutes(Date.now(), -maxTimeInterval), JSON.parse(sessionValue)]) - } - } - - const id = setInterval(() => { + const updateRecentFailedMissions = () => { const lastDismissTime: Date = getLastDismissalTime() BackendAPICaller.getMissionRuns({ statuses: [MissionStatus.Failed], pageSize: pageSize }).then( (missions) => { const newRecentFailedMissions = missions.content.filter( - (m) => new Date(m.endTime!) > lastDismissTime + (m) => + new Date(m.endTime!) > lastDismissTime && + (!installationCode || + m.installationCode!.toLocaleLowerCase() !== installationCode.toLocaleLowerCase()) ) if (newRecentFailedMissions.length > 0) setNewFailedMissions(newRecentFailedMissions) + setRecentFailedMissions(newRecentFailedMissions) } ) - }, refreshInterval) - return () => clearInterval(id) - }, []) + } + if (!recentFailedMissions || recentFailedMissions.length === 0) updateRecentFailedMissions() + }, [installationCode]) + + // Register a signalR event handler that listens for new failed missions + useEffect(() => { + if (connectionReady) + registerEvent(SignalREventLabels.missionRunFailed, (username: string, message: string) => { + const newFailedMission: Mission = JSON.parse(message) + const lastDismissTime: Date = getLastDismissalTime() + + setRecentFailedMissions((failedMissions) => { + if ( + installationCode && + (!newFailedMission.installationCode || + newFailedMission.installationCode.toLocaleLowerCase() !== + installationCode.toLocaleLowerCase()) + ) + return failedMissions // Ignore missions for other installations + // Ignore missions shortly after the user dismissed the last one + if (new Date(newFailedMission.endTime!) <= lastDismissTime) return failedMissions + let isDuplicate = failedMissions.filter((m) => m.id === newFailedMission.id).length > 0 + if (isDuplicate) return failedMissions // Ignore duplicate failed missions + return [...failedMissions, newFailedMission] + }) + }) + }, [registerEvent, connectionReady]) useEffect(() => { if (newFailedMissions.length > 0) { From b9afb98ec8d7ef90d61ddbb37a9548b8ecf0659a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Chirico=20Indreb=C3=B8?= Date: Fri, 10 Nov 2023 11:58:30 +0100 Subject: [PATCH 7/7] Make alert button background red on hover --- .../src/components/Alerts/FailedMissionAlert.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Alerts/FailedMissionAlert.tsx b/frontend/src/components/Alerts/FailedMissionAlert.tsx index 856cc6b83..779a238a3 100644 --- a/frontend/src/components/Alerts/FailedMissionAlert.tsx +++ b/frontend/src/components/Alerts/FailedMissionAlert.tsx @@ -10,6 +10,12 @@ const Indent = styled.div` padding: 0px 9px; ` +const StyledButton = styled(Button)` + :hover { + background-color: #ff9797; + } +` + interface MissionsProps { missions: Mission[] } @@ -24,10 +30,10 @@ function FailedMission({ missions }: MissionsProps) { } return ( - + ) } @@ -40,10 +46,10 @@ function SeveralFailedMissions({ missions }: MissionsProps) { } return ( - + ) }