Merge pull request #53 from input-output-hk/DAC-350
DAC-350 Time-run beneath the Labels in the timeline
DAC-350 Time-run beneath the Labels in the timeline
useEffect(() => {
bottomRef.current?.scrollIntoView({behavior: 'smooth'}); // scroll to bottom
logContentRef.current?.scrollIntoView({behavior: 'smooth'})
}, [logs])
const showLogView = () => {
setShowLogs(true);
const timeout = setTimeout(() => {
clearTimeout(timeout)
bottomRef.current?.scrollIntoView({behavior: 'smooth'}); // scroll to bottom
logContentRef.current?.scrollIntoView({behavior: 'smooth'})
}, 2);
}
text: string;
state?: string;
progress?: any;
runTimeTaken?: string;
}
unitTestSuccess?: boolean,
hasFailedTasks?: boolean
}
const TimelineItem: FC<ITimelineItem> = ({
unitTestSuccess,
config: { status, text, state, progress },
config: { status, text, state, progress, runTimeTaken },
hasFailedTasks
}) => {
<span className="text" data-testid={text}>
{text}
</span>
{runTimeTaken ? <span className="small-text">{runTimeTaken}</span> : null}
</li>
);
};
margin-left: 10px;
font-size: 18px;
}
span.small-text {
font-size: small;
}
span.progress-percentage {
position: absolute;
import TimelineItem from "components/TimelineItem/TimelineItem";
import "./Timeline.scss";
import { useAppSelector } from "store/store";
const Timeline = (props: any) => {
const { statusConfig, unitTestSuccess, hasFailedTasks } = props;
const { buildInfo } = useAppSelector((state) => state.runTime);
return (
<div id="statusTimeline" data-testid="statusTimeline">
<ul>
{statusConfig.map(
(config: any, index: React.Key | null | undefined) => (
(config: any, index: React.Key | null | undefined) => {
if (buildInfo.runState === config.status) {
config.runTimeTaken = buildInfo.runTime
}
return (
<TimelineItem key={index} config={config} unitTestSuccess={unitTestSuccess} hasFailedTasks={hasFailedTasks}/>
)
)
}
)}
</ul>
</div>
import React, { useEffect, useState } from "react";
import { fetchData,} from "api/api";
import { useEffect, useState } from "react";
import { useAppSelector } from "store/store";
import { useDispatch } from "react-redux";
import { useDelayedApi } from "hooks/useDelayedApi";
type Log = {
Time: string,
}
import { fetchData } from "api/api";
import { Log } from '../pages/certification/Certification.helper'
import { setStates, setEnded, setBuildInfo } from "pages/certification/slices/logRunTime.slice";
const TIMEOFFSET = 1000;
testEnded: boolean,
handleErrorScenario: () => void
) => {
const dispatch = useDispatch()
const [logInfo, setLogInfo] = useState<Log[]>([])
const [fetchingLogs, setFetchingLogs] = useState(false);
const [refetchLogsOffset] = useState(1);
const { startTime, endTime, runState, ended } = useAppSelector((state) => state.runTime)
const enabled = !fetchingLogs && !(ended > 1) && !!uuid
useEffect(() => {
if (testEnded) {
dispatch(setEnded(ended+1));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [testEnded])
//reset log if uuid changed
useEffect(()=>{
setLogInfo([])
dispatch(setEnded(0))
},[uuid])
const fetchLog = React.useCallback(async ()=>{
const captureRunTime = (sTime: string, eTime: string, state: string) => {
if (sTime && eTime && state) {
dispatch(setStates({startTime: sTime, endTime: eTime, runState: state}))
dispatch(setBuildInfo())
}
}
const computeRunState = (state: string) => {
if (state.indexOf('build-flake') !== -1) {
return 'building'
} else if (state.indexOf('generate-flake') !== -1) {
return 'preparing'
} else if (state.indexOf('run-certify') !== -1) {
return 'certifying'
} else {
return ''
}
}
const computeTimeForRunState = (currentLogs: Log[]) => {
let sTime: string = startTime, eTime: string = endTime, state: string = runState;
let triggeredStateChange = false
let lastLogItem = false;
const logSetLength = currentLogs.length - 1;
currentLogs.forEach((log, idx) => {
lastLogItem = idx === logSetLength
const currentRunState: string = computeRunState(log.Source)
if (!sTime) {
sTime = log.Time
eTime = log.Time
state = currentRunState
} else {
const lastLogEntry = ended === 1 && lastLogItem
if (state === currentRunState && !lastLogEntry) {
eTime = log.Time
if (lastLogItem) {
triggeredStateChange = false;
}
} else if (state && (state !== currentRunState || lastLogEntry)) {
captureRunTime(sTime, eTime, state)
state = currentRunState
sTime = log.Time
eTime = log.Time
triggeredStateChange = true
}
}
})
if (!triggeredStateChange) {
// save to useState to use in next logs
dispatch(setStates({startTime: sTime, endTime: eTime, runState: state}))
}
}
const fetchLog = async ()=>{
if(!uuid) return
const lastLogTimestamp = logInfo[logInfo.length - 1]?.Time
setFetchingLogs(true)
let fetchApi = false
if (testEnded && ended <= 1) {
fetchApi = true
dispatch(setEnded(ended+1));
}
try {
const queryAfter = lastLogTimestamp ? '?after=' + encodeURIComponent(lastLogTimestamp) : '';
const res: any = await fetchData.get("/run/" + uuid + "/logs" + queryAfter);
/** For mock */
// const res: any = await fetchData.get("static/data/build-logs.json")
if(res.data.length) setLogInfo((prev)=> prev.concat(res.data))
if(res.data.length) {
await computeTimeForRunState(res.data)
setLogInfo((prev)=> prev.concat(res.data))
} else if (fetchApi) {
// capture whatever is the last stored state
dispatch(setBuildInfo())
}
} catch(e) {
handleErrorScenario();
console.log(e);
} finally{
setFetchingLogs(false)
}
},[
logInfo,
uuid,
handleErrorScenario,
])
const enabled = !fetchingLogs && !testEnded && !!uuid
useDelayedApi(
fetchLog,
refetchLogsOffset * TIMEOFFSET,
enabled
)
return { logInfo,fetchingLogs}
}
useDelayedApi(
fetchLog,
refetchLogsOffset * TIMEOFFSET,
enabled
)
return { logInfo,fetchingLogs}
}
export const getPlannedCertificationTaskCount = (plannedTasks: any[]) => {
return plannedTasks.filter(item => item.name && typeof item.name === 'string').length
}
export interface Log {
Time: string,
Text: string,
Source: string
}
\ No newline at end of file
import { useAppDispatch, useAppSelector } from "store/store";
import { clearUuid, setUuid } from "./slices/certification.slice";
import { clearStates } from "./slices/logRunTime.slice";
import { deleteTestHistoryData } from "pages/testHistory/slices/deleteTestHistory.slice";
import { useConfirm } from "material-ui-confirm";
form.reset({
commit: "",
});
dispatch(clearStates())
}
const formHandler = (formData: ISearchForm) => {
hasFailedTasks={isAnyTaskFailure(resultData)}
/>
</div>
{runState ? (
<>
<InformationTable logs={logInfo} />
</>
) : null}
{/* To show 'View Logs' always */}
<InformationTable logs={logInfo} />
{unitTestSuccess === false && Object.keys(resultData).length ? (
<>
<ResultContainer unitTestSuccess={unitTestSuccess} result={resultData} />
import { createSlice } from "@reduxjs/toolkit";
import dayjs from "dayjs";
import { formatTimeToReadable } from "utils/utils";
interface RunTimeState {
startTime: string;
endTime: string;
runState: string;
ended: number;
buildInfo: {
runTime: string;
runState: string;
}
}
const initialState: RunTimeState = {
startTime: "",
endTime: "",
runState: "",
ended: 0,
buildInfo: {
runTime: "",
runState: ""
}
};
export const runTimeSlice = createSlice({
name: "runTime",
initialState,
reducers: {
setStartTime: (state, {payload}) => {
state.startTime = payload.startTime;
},
setEndTime: (state, {payload}) => {
state.endTime = payload.endTime;
},
setRunState: (state, {payload}) => {
state.runState = payload.runState;
},
setStates: (state, {payload}) => {
state.startTime = payload.startTime;
state.endTime = payload.endTime;
state.runState = payload.runState;
},
setEnded: (state, {payload}) => {
state.ended = payload
},
setBuildInfo: (state) => {
const msDiff: number = dayjs(state.endTime).diff(dayjs(state.startTime))
const timeStr: any = formatTimeToReadable(msDiff)
state.buildInfo = {
'runTime': timeStr,
'runState': state.runState
}
},
clearStates: () => initialState,
},
});
export const { setStartTime, setEndTime, setRunState, setStates, setEnded, setBuildInfo, clearStates } = runTimeSlice.actions;
export default runTimeSlice.reducer;
import authReducer from "./slices/auth.slice";
import certificationReducer from "pages/certification/slices/certification.slice";
import deleteTestHistory from "pages/testHistory/slices/deleteTestHistory.slice";
import logRunTimeSlice from "pages/certification/slices/logRunTime.slice";
const rootReducer = combineReducers({
auth: authReducer,
certification: certificationReducer,
deleteTestHistory
deleteTestHistory,
runTime: logRunTimeSlice
});
export type RootState = ReturnType<typeof rootReducer>;
const finalResult = result.charAt(0).toUpperCase() + result.slice(1);
return finalResult.trim();
}
}
export const formatTimeToReadable = (duration: number) => {
const milliseconds = Math.floor(duration % 1000),
seconds = Math.floor((duration / 1000) % 60),
minutes = Math.floor((duration / (1000 * 60)) % 60),
hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
let timeStr = '';
if (hours) {
timeStr += hours + 'h '
}
if (minutes) {
timeStr += minutes + 'm '
}
if (seconds) {
timeStr += seconds + 's '
}
if (milliseconds) {
timeStr += milliseconds + 'ms'
}
return timeStr
}
\ No newline at end of file
Bumps [bech32](https://github.com/rust-bitcoin/rust-bech32) from 0.8.1 to 0.9.1. - [Changelog](https://github.com/rust-bitcoin/rust-bech32/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-bitcoin/rust-bech32/compare/v0.8.1...v0.9.1) --- updated-dependencies: - dependency-name: bech32 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <[email protected]>
Bumps [log](https://github.com/rust-lang/log) from 0.4.17 to 0.4.18. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.17...0.4.18) --- updated-dependencies: - dependency-name: log dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.28.1 to 1.28.2. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.28.1...tokio-1.28.2) --- updated-dependencies: - dependency-name: tokio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
PLT-5901 Implemented checks for valid network addresses.
fix: tuple clause must preserve previous clause properties state
fix: rearrange clauses and fill in gaps now handles nested patterns in a uniform way fix: discards in records was being sorted incorrectly leading to type issues chore: remove some filter maps in cases where None is impossible anyway chore: some refactoring on a couple functions to clean up
fix: tuple clause must preserve previous clause properties state