Verified Commit 7b5425ae authored by Marek Veselý's avatar Marek Veselý
Browse files

feat: handle logs after/before exercise

parent def2b35e
Loading
Loading
Loading
Loading
+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,
  ])

@@ -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={{
@@ -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 (
@@ -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}
@@ -83,7 +122,7 @@ export const TimeRangeSelector: FC<TimeRangeSelectorProps> = ({
        })
      }
      stepSize={1000 * 60} // 1 minute steps
      labelStepSize={selectedTimeRangeInitialEnd / 5}
      labelValues={labelValues}
      labelRenderer={labelRenderer}
    />
  )
+25 −11
Original line number Diff line number Diff line
@@ -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) => {
@@ -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,
  }
+3 −1
Original line number Diff line number Diff line
@@ -79,9 +79,10 @@ const Plot = () => {
    earliestStartTime,
    latestStartTime,
    selectedTimeRange,
    selectedTimeRangeInitialStart,
    selectedTimeRangeInitialEnd,
    setSelectedTimeRange,
  } = useTimeRangeSelectorProps()
  } = useTimeRangeSelectorProps({})

  return (
    <div
@@ -98,6 +99,7 @@ const Plot = () => {
      >
        <TimeRangeSelector
          earliestStartTime={earliestStartTime}
          selectedTimeRangeInitialStart={selectedTimeRangeInitialStart}
          selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd}
          setSelectedTimeRange={setSelectedTimeRange}
        />
+3 −1
Original line number Diff line number Diff line
@@ -26,9 +26,10 @@ const RouteComponent = () => {
    earliestStartTime,
    latestStartTime,
    selectedTimeRange,
    selectedTimeRangeInitialStart,
    selectedTimeRangeInitialEnd,
    setSelectedTimeRange,
  } = useTimeRangeSelectorProps()
  } = useTimeRangeSelectorProps({})

  const teamSelectorProps = useTeamSelectorProps({
    teams,
@@ -74,6 +75,7 @@ const RouteComponent = () => {
      >
        <TimeRangeSelector
          earliestStartTime={earliestStartTime}
          selectedTimeRangeInitialStart={selectedTimeRangeInitialStart}
          selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd}
          setSelectedTimeRange={setSelectedTimeRange}
        />
+24 −19
Original line number Diff line number Diff line
@@ -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()
@@ -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({
@@ -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
@@ -124,6 +128,7 @@ const RouteComponent = () => {
        >
          <TimeRangeSelector
            earliestStartTime={earliestStartTime}
            selectedTimeRangeInitialStart={selectedTimeRangeInitialStart}
            selectedTimeRangeInitialEnd={selectedTimeRangeInitialEnd}
            setSelectedTimeRange={setSelectedTimeRange}
          />