export function rowDataReducer()

in src/components/Timeline/useTaskData.ts [78:213]


export function rowDataReducer(state: RowDataModel, action: RowDataAction): RowDataModel {
  switch (action.type) {
    case 'fillStep':
      // We got new step data. Add step objects BUT check if they already exists. Might happen if
      // Tasks requests is ready before step request.
      const steprows: RowDataModel = action.data.reduce((obj: RowDataModel, step: Step) => {
        const existingRow = obj[step.step_name];
        // If step object already exists, only add step data and calculate duration
        if (existingRow) {
          return {
            ...obj,
            [step.step_name]: {
              ...existingRow,
              step: step,
              isOpen: true,
              duration:
                existingRow.duration === existingRow.finished_at - step.ts_epoch
                  ? existingRow.duration
                  : existingRow.finished_at - step.ts_epoch,
            },
          };
        }
        // Else initialise empty step row object
        return {
          ...obj,
          [step.step_name]: {
            step: step,
            isOpen: true,
            status: 'unknown' as TaskStatus,
            finished_at: 0,
            duration: 0,
            data: {},
          },
        };
      }, state);

      return Object.keys(steprows)
        .sort((a, b) => {
          const astep = steprows[a];
          const bstep = steprows[b];
          return (astep.step?.ts_epoch || 0) - (bstep.step?.ts_epoch || 0);
        })
        .reduce((obj, key) => {
          return { ...obj, [key]: steprows[key] };
        }, {});

    case 'fillTasks': {
      // Group incoming tasks by step
      const grouped: Record<string, Task[]> = {};

      for (const row of action.data) {
        const step = row.step_name;

        if (grouped[step]) {
          grouped[step].push(row);
          // Make sure that we process attempts in correct attempt order. This is important when we try to figure out
          // start time and duration
          grouped[step] = grouped[step].sort((a, b) => a.attempt_id - b.attempt_id);
        } else {
          grouped[step] = [row];
        }
      }

      // And fill them to current state
      const newState = Object.keys(grouped).reduce((obj: RowDataModel, key: string): RowDataModel => {
        const row = obj[key];
        const newItems = grouped[key];
        const [startTime, endTime] = timepointsOfTasks(newItems);
        // Existing step entry
        if (row) {
          const newData = row.data;

          for (const item of newItems) {
            newData[item.task_id] = makeTasksForStep(newData, item);
          }

          const newEndTime = !row.finished_at || endTime > row.finished_at ? endTime : row.finished_at;

          return {
            ...obj,
            [key]: {
              ...row,
              status: getStepStatus(newData),
              finished_at: newEndTime,
              duration: row.step ? newEndTime - row.step.ts_epoch : row.duration,
              data: newData,
            },
          };
        }
        // New step entry

        const data = grouped[key].reduce<Record<number, Task[]>>((dataobj, item) => {
          return { ...dataobj, [item.task_id]: makeTasksForStep(dataobj, item) };
        }, {});

        return {
          ...obj,
          [key]: {
            isOpen: true,
            status: getStepStatus(data),
            finished_at: endTime,
            duration: startTime ? endTime - startTime : 0,
            data,
          },
        };
      }, state);

      return newState;
    }
    case 'toggle':
      if (state[action.id]) {
        return { ...state, [action.id]: { ...state[action.id], isOpen: !state[action.id].isOpen } };
      }
      return state;
    case 'open':
      if (state[action.id]) {
        return { ...state, [action.id]: { ...state[action.id], isOpen: true } };
      }
      return state;
    case 'close':
      if (state[action.id]) {
        return { ...state, [action.id]: { ...state[action.id], isOpen: false } };
      }
      return state;
    case 'openAll':
      return Object.keys(state).reduce((obj, current) => {
        return { ...obj, [current]: { ...obj[current], isOpen: true } };
      }, state);
    case 'closeAll':
      return Object.keys(state).reduce((obj, current) => {
        return { ...obj, [current]: { ...obj[current], isOpen: false } };
      }, state);
    case 'reset':
      return emptyObject;
  }
}