HabitatMap/AirCasting

View on GitHub
app/javascript/react/store/fixedSessionsSlice.ts

Summary

Maintainability
D
2 days
Test Coverage
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import { AxiosResponse } from "axios";
import { RootState } from ".";
import { oldApiClient } from "../api/apiClient";
import { API_ENDPOINTS } from "../api/apiEndpoints";
import { ApiError, StatusEnum } from "../types/api";
import { getErrorMessage } from "../utils/getErrorMessage";
import { logError } from "../utils/logController";
import { FixedSessionsTypes } from "./sessionFiltersSlice";

export interface FixedSession {
  id: number;
  uuid: string;
  endTimeLocal: string;
  startTimeLocal: string;
  lastMeasurementValue: number;
  isIndoor: boolean;
  latitude: number;
  longitude: number;
  title: string;
  username: string;
  streams: {
    [key: string]: {
      streamDailyAverage: number;
      measurementShortType: string;
      sensorName: string;
      unitSymbol: string;
      id: number;
    };
  };
}

interface SessionsResponse {
  fetchableSessionsCount: number;
  sessions: FixedSession[];
}

interface SessionsState {
  fetchableSessionsCount: number;
  activeSessions: FixedSession[];
  dormantSessions: FixedSession[];
  isActiveSessionsFetched: boolean;
  isDormantSessionsFetched: boolean;
  status: StatusEnum;
  error: ApiError | null;
}

export interface SessionsData {
  filters: string;
  isAdditional?: boolean;
}

const initialState: SessionsState = {
  fetchableSessionsCount: 0,
  activeSessions: [],
  dormantSessions: [],
  isActiveSessionsFetched: false,
  isDormantSessionsFetched: false,
  status: StatusEnum.Idle,
  error: null,
};

const createSessionFetchThunk = (
  type: FixedSessionsTypes,
  endpoint: (filters: string) => string
) => {
  return createAsyncThunk<
    SessionsResponse,
    SessionsData,
    { rejectValue: ApiError }
  >(
    `sessions/fetch${
      type.charAt(0).toUpperCase() + type.slice(1)
    }FixedSessions`,
    async (sessionsData, { rejectWithValue }) => {
      try {
        const response: AxiosResponse<SessionsResponse> =
          await oldApiClient.get(endpoint(sessionsData.filters));

        return response.data;
      } catch (error) {
        const message = getErrorMessage(error);

        const apiError: ApiError = {
          message,
          additionalInfo: {
            action: `fetch${
              type.charAt(0).toUpperCase() + type.slice(1)
            }FixedSessions`,
            endpoint: endpoint(sessionsData.filters),
          },
        };

        logError(error, apiError);

        return rejectWithValue(apiError);
      }
    },
    {
      condition: (_, { getState }) => {
        const { fixedSessions } = getState() as RootState;
        return fixedSessions.status !== StatusEnum.Pending;
      },
    }
  );
};

export const fetchActiveFixedSessions = createSessionFetchThunk(
  FixedSessionsTypes.ACTIVE,
  API_ENDPOINTS.fetchActiveFixedSessions
);

export const fetchDormantFixedSessions = createSessionFetchThunk(
  FixedSessionsTypes.DORMANT,
  API_ENDPOINTS.fetchDormantFixedSessions
);

const fixedSessionsSlice = createSlice({
  name: "fixedSessions",
  initialState,
  reducers: {
    cleanSessions(state) {
      state.fetchableSessionsCount = 0;
      state.activeSessions = [];
      state.dormantSessions = [];
      state.isActiveSessionsFetched = false;
      state.isDormantSessionsFetched = false;
      state.status = StatusEnum.Idle;
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchActiveFixedSessions.pending, (state) => {
        state.status = StatusEnum.Pending;
        state.error = null;
      })
      .addCase(
        fetchActiveFixedSessions.fulfilled,
        (state, action: PayloadAction<SessionsResponse>) => {
          state.status = StatusEnum.Fulfilled;
          state.activeSessions = action.payload.sessions;
          state.fetchableSessionsCount = action.payload.fetchableSessionsCount;
          state.isActiveSessionsFetched = true;
          state.error = null;
        }
      )
      .addCase(
        fetchActiveFixedSessions.rejected,
        (state, action: PayloadAction<ApiError | undefined>) => {
          state.status = StatusEnum.Rejected;
          state.error = action.payload || { message: "Unknown error occurred" };
        }
      );
    builder
      .addCase(fetchDormantFixedSessions.pending, (state) => {
        state.status = StatusEnum.Pending;
        state.error = null;
      })
      .addCase(fetchDormantFixedSessions.fulfilled, (state, action) => {
        state.status = StatusEnum.Fulfilled;
        state.dormantSessions = action.meta?.arg.isAdditional
          ? [...state.dormantSessions, ...action.payload.sessions]
          : action.payload.sessions;
        state.fetchableSessionsCount = action.payload.fetchableSessionsCount;
        state.isDormantSessionsFetched = true;
        state.error = null;
      })
      .addCase(
        fetchDormantFixedSessions.rejected,
        (state, action: PayloadAction<ApiError | undefined>) => {
          state.status = StatusEnum.Rejected;
          state.error = action.payload || { message: "Unknown error occurred" };
        }
      );
  },
});

export const { cleanSessions } = fixedSessionsSlice.actions;
export default fixedSessionsSlice.reducer;