diff --git a/backend/status/helper.ts b/backend/status/helper.ts index 172a06be..fdada369 100644 --- a/backend/status/helper.ts +++ b/backend/status/helper.ts @@ -1,4 +1,4 @@ -import Status, { Process, Stage, State } from 'types/status'; +import Status, { Duration, Process, Stage, State, StepState } from 'types/status'; const statusesExpire = 60 * 60 * 24 * 7; // 7 days const statusesTimeout = 60 * 60 * 2; // 2 hours @@ -40,7 +40,7 @@ export const getStuckStatuses = (statuses: Status[]): Status[] => return false; }); -export const fixStatusStates = (status: Status): Status => { +export const processStatusChanges = (status: Status): Status => { const processes = status.processes // Sort processes by creation time .sort( @@ -53,10 +53,41 @@ export const fixStatusStates = (status: Status): Status => { return { ...status, state: determineStatusState(processes), - processes, + processes: patchProcessDurations(processes), }; }; +const setDuration = (state: StepState | Process['state'], currentDuration?: Duration): Duration => { + if (state === 'running' || state === 'warning') { + return { + ran: currentDuration?.ran || 0, + start: currentDuration?.start ? currentDuration.start : new Date().toUTCString(), + }; + } + + let duration = currentDuration?.ran || 0; + if (currentDuration?.start) { + duration += Math.abs(new Date(currentDuration.start).getTime() - new Date().getTime()); + } + return { + ran: duration, + }; +}; + +export const patchProcessDurations = (processes: Process[]): Process[] => + processes.map((process) => ({ + ...process, + stages: process.stages.map((stage) => ({ + ...stage, + steps: stage.steps.map((step) => ({ + ...step, + duration: setDuration(step.state, step.duration), + })), + duration: setDuration(stage.state, stage.duration), + })), + duration: setDuration(process.state, process.duration), + })); + export const fixStuckStatus = (status: Status): Status => ({ ...status, processes: status.processes.map((process) => { diff --git a/backend/status/manager.ts b/backend/status/manager.ts index 037e6c4f..8a422ac3 100644 --- a/backend/status/manager.ts +++ b/backend/status/manager.ts @@ -2,7 +2,7 @@ import StorageManager from 'backend/storage/manager'; import Status from 'types/status'; import StatusEvents from './events'; -import { fixStatusStates, fixStuckStatus, getExpiredStatuses, getStuckStatuses } from './helper'; +import { fixStuckStatus, getExpiredStatuses, getStuckStatuses, processStatusChanges } from './helper'; class StatusManager { statuses: Status[] = []; @@ -46,7 +46,7 @@ class StatusManager { } setStatus(status: Status): void { - status = fixStatusStates(status); + status = processStatusChanges(status); let replacedStatus = false; const statuses = [ diff --git a/cypress/integration/github/push.spec.js b/cypress/integration/github/push.spec.js index ca221433..15415bfa 100644 --- a/cypress/integration/github/push.spec.js +++ b/cypress/integration/github/push.spec.js @@ -1,26 +1,27 @@ /// context('A running GitHub push', () => { - it('opens the CIMonitor dashboard', () => { - cy.visit('/'); - }); + it('opens the CIMonitor dashboard', () => { + cy.visit('/'); + }); - it('pushes a GitHub action flow', () => { - for (let count = 1; count <= 8; count++) { - if (count > 1) { - cy.wait(1000); - } - cy.github(`push-failed/${count}`); - } + it('pushes a GitHub failed action flow', () => { + for (let count = 1; count <= 8; count++) { + if (count > 1) { + cy.wait(1000); + } + cy.github(`push-failed/${count}`); + } - cy.wait(2000); + cy.wait(2000); + }); - // Push all created events - for (let count = 1; count <= 14; count++) { - if (count > 1) { - cy.wait(1000); - } - cy.github(`push/${count}`); - } - }); + it('pushes a GitHub successful action flow', () => { + for (let count = 1; count <= 14; count++) { + if (count > 1) { + cy.wait(1000); + } + cy.github(`push/${count}`); + } + }); }); diff --git a/frontend/App/Statuses/Status/RunTime.tsx b/frontend/App/Statuses/Status/RunTime.tsx new file mode 100644 index 00000000..9db1a03c --- /dev/null +++ b/frontend/App/Statuses/Status/RunTime.tsx @@ -0,0 +1,48 @@ +import { ReactElement, useEffect, useState } from 'react'; + +import { Duration } from '/types/status'; + +const getTimeRan = (milliseconds: number = 0) => { + if (milliseconds === 0) { + return ''; + } + + let seconds = Math.round(milliseconds / 1000); + const minutes = Math.floor(seconds / 60); + seconds = seconds - minutes * 60; + + return `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`; +}; + +type Props = { + duration?: Duration; +}; + +const RunTime = ({ duration }: Props): ReactElement | null => { + const [runDuration, setRunDuration] = useState(duration?.ran || 0); + + useEffect(() => { + let intervalId = undefined; + + const updateTime = (duration: Duration) => { + const ran = duration?.ran || 0; + + if (!duration?.start) { + return ran; + } + + setRunDuration(Math.abs(new Date(duration.start).getTime() - new Date().getTime()) + ran); + }; + + if (duration.start) { + intervalId = setInterval(() => updateTime(duration), 1000); + } + + updateTime(duration); + + return () => clearInterval(intervalId); + }, [duration, setRunDuration]); + return {getTimeRan(runDuration)}; +}; + +export default RunTime; diff --git a/frontend/App/Statuses/Status/Status.tsx b/frontend/App/Statuses/Status/Status.tsx index b051c4ee..46802bd6 100644 --- a/frontend/App/Statuses/Status/Status.tsx +++ b/frontend/App/Statuses/Status/Status.tsx @@ -2,6 +2,7 @@ import { ReactElement } from 'react'; import { Body, Box, Boxes, Container, Details, LinkBox, Project, UserImage } from './Status.style'; +import RunTime from '/frontend/App/Statuses/Status/RunTime'; import Icon from '/frontend/components/Icon'; import useSetting from '/frontend/hooks/useSetting'; @@ -28,6 +29,8 @@ const pettyUrl = (url: string) => const Statuses = ({ status }: Props): ReactElement => { const showAvatars = useSetting('showAvatars'); + const activeProcess = status.processes[0] || null; + return ( @@ -56,6 +59,11 @@ const Statuses = ({ status }: Props): ReactElement => { + {!!activeProcess && ( + + + + )} {!!status.userImage && showAvatars && ( diff --git a/types/status.ts b/types/status.ts index 230a1793..3b7d45bc 100644 --- a/types/status.ts +++ b/types/status.ts @@ -11,12 +11,17 @@ export type StepState = | 'timeout' | 'stopped'; +export type Duration = { + start?: string; + ran: number; +}; + export type Step = { id: string; title: string; state: StepState; time: string; - duration?: number; + duration?: Duration; }; export type Stage = { @@ -25,6 +30,7 @@ export type Stage = { state: StepState; steps: Step[]; time: string; + duration?: Duration; }; export type Process = { @@ -33,7 +39,7 @@ export type Process = { state: State; stages: Stage[]; time: string; - duration?: number; + duration?: Duration; }; export type Status = {