Loading analyst/src/components/Plots/TimeScatterPlot/TimeRangeSelector/index.tsx +50 −11 Original line number Diff line number Diff line import type { NumberRange } from '@blueprintjs/core' import { RangeSlider } from '@blueprintjs/core' import { RangeSlider, Tag } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { Timestamp, useTimeFormat } from '@inject/shared' import { useState, type Dispatch, type FC, type SetStateAction } from 'react' import { useContext, useState, type Dispatch, type FC, type SetStateAction, } from 'react' import { ExerciseContext } from '../../../ExerciseContext' import { MIN_TO_MS } from '../../../utilities' import type { TimeRange } from '../types' interface TimeRangeSelectorProps { earliestStartTime: number selectedTimeRangeInitialStart: number selectedTimeRangeInitialEnd: number setSelectedTimeRange: Dispatch<SetStateAction<TimeRange>> } export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ earliestStartTime, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, }) => { const timeFormat = useTimeFormat() const { exercise } = useContext(ExerciseContext) // a separate state to avoid re-rendering during slider drag const [sliderRange, setSliderRange] = useState<NumberRange>([ 0, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, ]) Loading @@ -31,13 +42,7 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ } ) => { const children = (() => { if (!opts?.isHandleTooltip && value === 0) { return 'Exercise Started' } if (!opts?.isHandleTooltip && value === selectedTimeRangeInitialEnd) { return 'Duration Expired' } return ( const timestamp = ( <Timestamp stringOnly formatTimestampProps={{ Loading @@ -52,6 +57,31 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ }} /> ) if (opts?.isHandleTooltip) { return timestamp } if (value === 0) { return ( <Tag intent='primary' minimal> Exercise Started </Tag> ) } if (value === exercise.config.exerciseDuration * MIN_TO_MS) { return ( <Tag intent='primary' minimal> Duration Expired </Tag> ) } if (value === selectedTimeRangeInitialStart) { return <Tag minimal>Earliest Log</Tag> } if (value === selectedTimeRangeInitialEnd) { return <Tag minimal>Latest Log</Tag> } return null })() return ( Loading @@ -70,6 +100,15 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ ) } const labelValues = Array.from( new Set([ selectedTimeRangeInitialStart, 0, selectedTimeRangeInitialEnd, exercise.config.exerciseDuration * MIN_TO_MS, ]) ).sort((a, b) => a - b) return ( <RangeSlider min={0} Loading @@ -83,7 +122,7 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ }) } stepSize={1000 * 60} // 1 minute steps labelStepSize={selectedTimeRangeInitialEnd / 5} labelValues={labelValues} labelRenderer={labelRenderer} /> ) Loading analyst/src/components/Plots/TimeScatterPlot/TimeRangeSelector/useTimeRangeSelectorProps.ts +25 −11 Original line number Diff line number Diff line Loading @@ -2,19 +2,15 @@ import { useContext, useState } from 'react' import { ExerciseContext } from '../../../ExerciseContext' import { MIN_TO_MS } from '../../../utilities' export const useTimeRangeSelectorProps = () => { export const useTimeRangeSelectorProps = ({ earliestLogTime, latestLogTime, }: { earliestLogTime?: number latestLogTime?: number }) => { const { exercise } = useContext(ExerciseContext) // TODO: add paused time (elapsedS) const selectedTimeRangeInitialEnd = exercise.config.exerciseDuration * MIN_TO_MS const [selectedTimeRange, setSelectedTimeRange] = useState<{ rangeStart: number rangeEnd: number }>({ rangeStart: 0, rangeEnd: selectedTimeRangeInitialEnd, }) const { earliest: earliestStartTimeDirty, latest: latestStartTimeDirty } = exercise.states.reduce( ({ earliest, latest }, state) => { Loading @@ -38,10 +34,28 @@ export const useTimeRangeSelectorProps = () => { const latestStartTime = latestStartTimeDirty > -Infinity ? latestStartTimeDirty : 0 // TODO: add paused time (elapsedS) const selectedTimeRangeInitialStart = Math.min( earliestLogTime ? earliestLogTime - earliestLogTime : Infinity, 0 ) const selectedTimeRangeInitialEnd = Math.max( latestLogTime ? latestLogTime - earliestStartTime : -Infinity, exercise.config.exerciseDuration * MIN_TO_MS ) const [selectedTimeRange, setSelectedTimeRange] = useState<{ rangeStart: number rangeEnd: number }>({ rangeStart: selectedTimeRangeInitialStart, rangeEnd: selectedTimeRangeInitialEnd, }) return { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } Loading analyst/src/components/ToolUsage/InTimeToolUsage/Plot.tsx +3 −1 Original line number Diff line number Diff line Loading @@ -79,9 +79,10 @@ const Plot = () => { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() } = useTimeRangeSelectorProps({}) return ( <div Loading @@ -98,6 +99,7 @@ const Plot = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading analyst/src/routes/_layout/$exerciseId/action-logs.tsx +3 −1 Original line number Diff line number Diff line Loading @@ -26,9 +26,10 @@ const RouteComponent = () => { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() } = useTimeRangeSelectorProps({}) const teamSelectorProps = useTeamSelectorProps({ teams, Loading Loading @@ -74,6 +75,7 @@ const RouteComponent = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading analyst/src/routes/_layout/$exerciseId/sandbox-logs.tsx +24 −19 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import { useTeamSelectorProps } from '../../../components/TeamSelector/useTeamSe // TODO: when all teams selected, show how many logs per team // a) inside the histogram // b) a separate bar chart // TODO: allow logs outside of time range (outside of exercise time) const RouteComponent = () => { const { exerciseId } = SandboxLogsRoute.useParams() Loading @@ -48,15 +47,6 @@ const RouteComponent = () => { prevTeamIdRef.current = selectedTeamId }, [selectedTeamId, teamLimitDefault]) // TODO: add first and last timestamp so that logs outside of the exercise can also be shown (e.g. sandbox logs from before the exercise started) // TODO: also in other places where time range selector is used const { earliestStartTime, selectedTimeRange, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() // TODO: test if works when changing teams const teamIds = selectedTeamId ? [selectedTeamId] : teams.map(t => t.id) const [{ data, fetching }] = useTypedQuery({ Loading @@ -77,15 +67,29 @@ const RouteComponent = () => { pause: !teamIds.length, }) const sandboxLogsInRange = data ? (data.teamActionLogs as SandboxLogChecked[]).filter(actionLog => { const sandboxLogs = data ? (data.teamActionLogs as SandboxLogChecked[]) : [] // newestFirst const earliestLog = sandboxLogs.at(-1)?.timestamp const latestLog = sandboxLogs.at(0)?.timestamp const { earliestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps({ earliestLogTime: earliestLog ? new Date(earliestLog).getTime() : undefined, latestLogTime: latestLog ? new Date(latestLog).getTime() : undefined, }) const sandboxLogsInRange = sandboxLogs.filter(actionLog => { const actionLogTime = new Date(actionLog.timestamp).getTime() return ( actionLogTime >= earliestStartTime + selectedTimeRange.rangeStart && actionLogTime <= earliestStartTime + selectedTimeRange.rangeEnd ) }) : [] return ( <main Loading Loading @@ -124,6 +128,7 @@ const RouteComponent = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading Loading
analyst/src/components/Plots/TimeScatterPlot/TimeRangeSelector/index.tsx +50 −11 Original line number Diff line number Diff line import type { NumberRange } from '@blueprintjs/core' import { RangeSlider } from '@blueprintjs/core' import { RangeSlider, Tag } from '@blueprintjs/core' import { css, cx } from '@emotion/css' import { Timestamp, useTimeFormat } from '@inject/shared' import { useState, type Dispatch, type FC, type SetStateAction } from 'react' import { useContext, useState, type Dispatch, type FC, type SetStateAction, } from 'react' import { ExerciseContext } from '../../../ExerciseContext' import { MIN_TO_MS } from '../../../utilities' import type { TimeRange } from '../types' interface TimeRangeSelectorProps { earliestStartTime: number selectedTimeRangeInitialStart: number selectedTimeRangeInitialEnd: number setSelectedTimeRange: Dispatch<SetStateAction<TimeRange>> } export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ earliestStartTime, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, }) => { const timeFormat = useTimeFormat() const { exercise } = useContext(ExerciseContext) // a separate state to avoid re-rendering during slider drag const [sliderRange, setSliderRange] = useState<NumberRange>([ 0, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, ]) Loading @@ -31,13 +42,7 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ } ) => { const children = (() => { if (!opts?.isHandleTooltip && value === 0) { return 'Exercise Started' } if (!opts?.isHandleTooltip && value === selectedTimeRangeInitialEnd) { return 'Duration Expired' } return ( const timestamp = ( <Timestamp stringOnly formatTimestampProps={{ Loading @@ -52,6 +57,31 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ }} /> ) if (opts?.isHandleTooltip) { return timestamp } if (value === 0) { return ( <Tag intent='primary' minimal> Exercise Started </Tag> ) } if (value === exercise.config.exerciseDuration * MIN_TO_MS) { return ( <Tag intent='primary' minimal> Duration Expired </Tag> ) } if (value === selectedTimeRangeInitialStart) { return <Tag minimal>Earliest Log</Tag> } if (value === selectedTimeRangeInitialEnd) { return <Tag minimal>Latest Log</Tag> } return null })() return ( Loading @@ -70,6 +100,15 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ ) } const labelValues = Array.from( new Set([ selectedTimeRangeInitialStart, 0, selectedTimeRangeInitialEnd, exercise.config.exerciseDuration * MIN_TO_MS, ]) ).sort((a, b) => a - b) return ( <RangeSlider min={0} Loading @@ -83,7 +122,7 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({ }) } stepSize={1000 * 60} // 1 minute steps labelStepSize={selectedTimeRangeInitialEnd / 5} labelValues={labelValues} labelRenderer={labelRenderer} /> ) Loading
analyst/src/components/Plots/TimeScatterPlot/TimeRangeSelector/useTimeRangeSelectorProps.ts +25 −11 Original line number Diff line number Diff line Loading @@ -2,19 +2,15 @@ import { useContext, useState } from 'react' import { ExerciseContext } from '../../../ExerciseContext' import { MIN_TO_MS } from '../../../utilities' export const useTimeRangeSelectorProps = () => { export const useTimeRangeSelectorProps = ({ earliestLogTime, latestLogTime, }: { earliestLogTime?: number latestLogTime?: number }) => { const { exercise } = useContext(ExerciseContext) // TODO: add paused time (elapsedS) const selectedTimeRangeInitialEnd = exercise.config.exerciseDuration * MIN_TO_MS const [selectedTimeRange, setSelectedTimeRange] = useState<{ rangeStart: number rangeEnd: number }>({ rangeStart: 0, rangeEnd: selectedTimeRangeInitialEnd, }) const { earliest: earliestStartTimeDirty, latest: latestStartTimeDirty } = exercise.states.reduce( ({ earliest, latest }, state) => { Loading @@ -38,10 +34,28 @@ export const useTimeRangeSelectorProps = () => { const latestStartTime = latestStartTimeDirty > -Infinity ? latestStartTimeDirty : 0 // TODO: add paused time (elapsedS) const selectedTimeRangeInitialStart = Math.min( earliestLogTime ? earliestLogTime - earliestLogTime : Infinity, 0 ) const selectedTimeRangeInitialEnd = Math.max( latestLogTime ? latestLogTime - earliestStartTime : -Infinity, exercise.config.exerciseDuration * MIN_TO_MS ) const [selectedTimeRange, setSelectedTimeRange] = useState<{ rangeStart: number rangeEnd: number }>({ rangeStart: selectedTimeRangeInitialStart, rangeEnd: selectedTimeRangeInitialEnd, }) return { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } Loading
analyst/src/components/ToolUsage/InTimeToolUsage/Plot.tsx +3 −1 Original line number Diff line number Diff line Loading @@ -79,9 +79,10 @@ const Plot = () => { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() } = useTimeRangeSelectorProps({}) return ( <div Loading @@ -98,6 +99,7 @@ const Plot = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading
analyst/src/routes/_layout/$exerciseId/action-logs.tsx +3 −1 Original line number Diff line number Diff line Loading @@ -26,9 +26,10 @@ const RouteComponent = () => { earliestStartTime, latestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() } = useTimeRangeSelectorProps({}) const teamSelectorProps = useTeamSelectorProps({ teams, Loading Loading @@ -74,6 +75,7 @@ const RouteComponent = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading
analyst/src/routes/_layout/$exerciseId/sandbox-logs.tsx +24 −19 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import { useTeamSelectorProps } from '../../../components/TeamSelector/useTeamSe // TODO: when all teams selected, show how many logs per team // a) inside the histogram // b) a separate bar chart // TODO: allow logs outside of time range (outside of exercise time) const RouteComponent = () => { const { exerciseId } = SandboxLogsRoute.useParams() Loading @@ -48,15 +47,6 @@ const RouteComponent = () => { prevTeamIdRef.current = selectedTeamId }, [selectedTeamId, teamLimitDefault]) // TODO: add first and last timestamp so that logs outside of the exercise can also be shown (e.g. sandbox logs from before the exercise started) // TODO: also in other places where time range selector is used const { earliestStartTime, selectedTimeRange, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps() // TODO: test if works when changing teams const teamIds = selectedTeamId ? [selectedTeamId] : teams.map(t => t.id) const [{ data, fetching }] = useTypedQuery({ Loading @@ -77,15 +67,29 @@ const RouteComponent = () => { pause: !teamIds.length, }) const sandboxLogsInRange = data ? (data.teamActionLogs as SandboxLogChecked[]).filter(actionLog => { const sandboxLogs = data ? (data.teamActionLogs as SandboxLogChecked[]) : [] // newestFirst const earliestLog = sandboxLogs.at(-1)?.timestamp const latestLog = sandboxLogs.at(0)?.timestamp const { earliestStartTime, selectedTimeRange, selectedTimeRangeInitialStart, selectedTimeRangeInitialEnd, setSelectedTimeRange, } = useTimeRangeSelectorProps({ earliestLogTime: earliestLog ? new Date(earliestLog).getTime() : undefined, latestLogTime: latestLog ? new Date(latestLog).getTime() : undefined, }) const sandboxLogsInRange = sandboxLogs.filter(actionLog => { const actionLogTime = new Date(actionLog.timestamp).getTime() return ( actionLogTime >= earliestStartTime + selectedTimeRange.rangeStart && actionLogTime <= earliestStartTime + selectedTimeRange.rangeEnd ) }) : [] return ( <main Loading Loading @@ -124,6 +128,7 @@ const RouteComponent = () => { > <TimeRangeSelector earliestStartTime={earliestStartTime} selectedTimeRangeInitialStart={selectedTimeRangeInitialStart} selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd} setSelectedTimeRange={setSelectedTimeRange} /> Loading