Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show process durations #193

Merged
merged 4 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions backend/status/helper.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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) => {
Expand Down
4 changes: 2 additions & 2 deletions backend/status/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -46,7 +46,7 @@ class StatusManager {
}

setStatus(status: Status): void {
status = fixStatusStates(status);
status = processStatusChanges(status);
let replacedStatus = false;

const statuses = [
Expand Down
39 changes: 20 additions & 19 deletions cypress/integration/github/push.spec.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
/// <reference types="cypress" />

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}`);
}
});
});
48 changes: 48 additions & 0 deletions frontend/App/Statuses/Status/RunTime.tsx
Original file line number Diff line number Diff line change
@@ -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 <span>{getTimeRan(runDuration)}</span>;
};

export default RunTime;
8 changes: 8 additions & 0 deletions frontend/App/Statuses/Status/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -28,6 +29,8 @@ const pettyUrl = (url: string) =>
const Statuses = ({ status }: Props): ReactElement => {
const showAvatars = useSetting('showAvatars');

const activeProcess = status.processes[0] || null;

return (
<Container key={status.id} state={status.state}>
<Body>
Expand Down Expand Up @@ -56,6 +59,11 @@ const Statuses = ({ status }: Props): ReactElement => {
<Box>
<Icon icon="schedule" /> <TimePassed since={status.time} />
</Box>
{!!activeProcess && (
<Box>
<Icon icon="timer" /> <RunTime duration={activeProcess.duration} />
</Box>
)}
</Boxes>
</Details>
{!!status.userImage && showAvatars && (
Expand Down
10 changes: 8 additions & 2 deletions types/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -25,6 +30,7 @@ export type Stage = {
state: StepState;
steps: Step[];
time: string;
duration?: Duration;
};

export type Process = {
Expand All @@ -33,7 +39,7 @@ export type Process = {
state: State;
stages: Stage[];
time: string;
duration?: number;
duration?: Duration;
};

export type Status = {
Expand Down