diff --git a/app/src/actions/jobActions.ts b/app/src/actions/jobActions.ts deleted file mode 100644 index 0a83ace62c..0000000000 --- a/app/src/actions/jobActions.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * This file contains all redux actions that can be executed on jobs - */ - -// Constants of action types for fetching jobs from server -export const LOAD_JOBS_IN_PROGRESS = "LOAD_JOBS_IN_PROGRESS"; -export const LOAD_JOBS_SUCCESS = "LOAD_JOBS_SUCCESS"; -export const LOAD_JOBS_FAILURE = "LOAD_JOBS_FAILURE"; - -// Constants of action types affecting UI -export const SET_JOB_COLUMNS = "SET_JOB_COLUMNS"; - -// Actions affecting fetching jobs from server - -export const loadJobsInProgress = () => ({ - type: LOAD_JOBS_IN_PROGRESS, -}); - -// @ts-expect-error TS(7006): Parameter 'jobs' implicitly has an 'any' type. -export const loadJobsSuccess = (jobs) => ({ - type: LOAD_JOBS_SUCCESS, - payload: { jobs }, -}); - -export const loadJobsFailure = () => ({ - type: LOAD_JOBS_FAILURE, -}); - -// Actions affecting UI - -// @ts-expect-error TS(7006): Parameter 'updatedColumns' implicitly has an 'any'... Remove this comment to see the full error message -export const setJobColumns = (updatedColumns) => ({ - type: SET_JOB_COLUMNS, - payload: { updatedColumns }, -}); diff --git a/app/src/components/shared/MainNav.tsx b/app/src/components/shared/MainNav.tsx index 1f00135b41..da8d097df5 100644 --- a/app/src/components/shared/MainNav.tsx +++ b/app/src/components/shared/MainNav.tsx @@ -16,7 +16,6 @@ import { } from "../../thunks/tableThunks"; import { fetchEvents } from "../../slices/eventSlice"; import { fetchRecordings } from "../../thunks/recordingThunks"; -import { fetchJobs } from "../../thunks/jobThunks"; import { fetchUsers } from "../../thunks/userThunks"; import { fetchThemes } from "../../thunks/themeThunks"; import { fetchFilters, fetchStats } from "../../thunks/tableFilterThunks"; @@ -31,6 +30,7 @@ import { GlobalHotKeys } from "react-hotkeys"; import { availableHotkeys } from "../../configs/hotkeysConfig"; import { fetchAcls } from "../../slices/aclSlice"; import { useAppDispatch } from "../../store"; +import { fetchJobs } from "../../slices/jobSlice"; /** * This component renders the main navigation that opens when the burger button is clicked @@ -52,8 +52,6 @@ const MainNav = ({ loadingRecordings, // @ts-expect-error TS(7031): Binding element 'loadingRecordingsIntoTable' impli... Remove this comment to see the full error message loadingRecordingsIntoTable, -// @ts-expect-error TS(7031): Binding element 'loadingJobs' implicitly has an 'a... Remove this comment to see the full error message - loadingJobs, // @ts-expect-error TS(7031): Binding element 'loadingJobsIntoTable' implicitly ... Remove this comment to see the full error message loadingJobsIntoTable, // @ts-expect-error TS(7031): Binding element 'loadingServers' implicitly has an... Remove this comment to see the full error message @@ -138,7 +136,7 @@ const MainNav = ({ resetOffset(); // Fetching jobs from server - loadingJobs(); + dispatch(fetchJobs()); // Load jobs into table loadingJobsIntoTable(); @@ -348,7 +346,6 @@ const mapDispatchToProps = (dispatch) => ({ // @ts-expect-error TS(2554): Expected 1 arguments, but got 0. loadingRecordings: () => dispatch(fetchRecordings()), loadingRecordingsIntoTable: () => dispatch(loadRecordingsIntoTable()), - loadingJobs: () => dispatch(fetchJobs()), loadingJobsIntoTable: () => dispatch(loadJobsIntoTable()), loadingServers: () => dispatch(fetchServers()), loadingServersIntoTable: () => dispatch(loadServersIntoTable()), diff --git a/app/src/components/systems/Jobs.tsx b/app/src/components/systems/Jobs.tsx index d6635ee521..d4de220e92 100644 --- a/app/src/components/systems/Jobs.tsx +++ b/app/src/components/systems/Jobs.tsx @@ -10,7 +10,6 @@ import Notifications from "../shared/Notifications"; import { jobsTemplateMap } from "../../configs/tableConfigs/jobsTableConfig"; import { getTotalJobs } from "../../selectors/jobSelectors"; import { fetchFilters } from "../../thunks/tableFilterThunks"; -import { fetchJobs } from "../../thunks/jobThunks"; import { loadJobsIntoTable, loadServersIntoTable, @@ -26,17 +25,15 @@ import Footer from "../Footer"; import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; +import { useAppDispatch, useAppSelector } from "../../store"; +import { fetchJobs } from "../../slices/jobSlice"; /** * This component renders the table view of jobs */ const Jobs = ({ -// @ts-expect-error TS(7031): Binding element 'loadingJobs' implicitly has an 'a... Remove this comment to see the full error message - loadingJobs, // @ts-expect-error TS(7031): Binding element 'loadingJobsIntoTable' implicitly ... Remove this comment to see the full error message loadingJobsIntoTable, -// @ts-expect-error TS(7031): Binding element 'jobs' implicitly has an 'any' typ... Remove this comment to see the full error message - jobs, // @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message loadingFilters, // @ts-expect-error TS(7031): Binding element 'loadingServers' implicitly has an... Remove this comment to see the full error message @@ -57,11 +54,19 @@ const Jobs = ({ currentFilterType, }) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [displayNavigation, setNavigation] = useState(false); + const jobs = useAppSelector(state => getTotalJobs(state)); + + // TODO: Get rid of the wrappers when modernizing redux is done + const fetchJobsWrapper = () => { + dispatch(fetchJobs()) + } + const loadJobs = async () => { // Fetching jobs from server - await loadingJobs(); + await dispatch(fetchJobs()); // Load jobs into table loadingJobsIntoTable(); @@ -158,7 +163,7 @@ const Jobs = ({
{/* Include filters component */} @@ -176,7 +181,6 @@ const Jobs = ({ // Getting state data out of redux store // @ts-expect-error TS(7006): Parameter 'state' implicitly has an 'any' type. const mapStateToProps = (state) => ({ - jobs: getTotalJobs(state), user: getUserInformation(state), currentFilterType: getCurrentFilterResource(state), }); @@ -186,7 +190,6 @@ const mapStateToProps = (state) => ({ const mapDispatchToProps = (dispatch) => ({ // @ts-expect-error TS(7006): Parameter 'resource' implicitly has an 'any' type. loadingFilters: (resource) => dispatch(fetchFilters(resource)), - loadingJobs: () => dispatch(fetchJobs()), loadingJobsIntoTable: () => dispatch(loadJobsIntoTable()), loadingServers: () => dispatch(fetchServers()), loadingServersIntoTable: () => dispatch(loadServersIntoTable()), diff --git a/app/src/components/systems/Servers.tsx b/app/src/components/systems/Servers.tsx index 13e6453046..771475f62c 100644 --- a/app/src/components/systems/Servers.tsx +++ b/app/src/components/systems/Servers.tsx @@ -16,7 +16,6 @@ import { loadServersIntoTable, loadServicesIntoTable, } from "../../thunks/tableThunks"; -import { fetchJobs } from "../../thunks/jobThunks"; import { fetchServices } from "../../thunks/serviceThunks"; import { editTextFilter } from "../../actions/tableFilterActions"; import { setOffset } from "../../actions/tableActions"; @@ -26,6 +25,8 @@ import Footer from "../Footer"; import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; +import { useAppDispatch } from "../../store"; +import { fetchJobs } from "../../slices/jobSlice"; /** * This component renders the table view of servers @@ -39,8 +40,6 @@ const Servers = ({ servers, // @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message loadingFilters, -// @ts-expect-error TS(7031): Binding element 'loadingJobs' implicitly has an 'a... Remove this comment to see the full error message - loadingJobs, // @ts-expect-error TS(7031): Binding element 'loadingJobsIntoTable' implicitly ... Remove this comment to see the full error message loadingJobsIntoTable, // @ts-expect-error TS(7031): Binding element 'loadingServices' implicitly has a... Remove this comment to see the full error message @@ -57,6 +56,7 @@ const Servers = ({ currentFilterType, }) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [displayNavigation, setNavigation] = useState(false); const loadServers = async () => { @@ -72,7 +72,7 @@ const Servers = ({ resetOffset(); // Fetching jobs from server - loadingJobs(); + dispatch(fetchJobs()); // Load jobs into table loadingJobsIntoTable(); @@ -188,7 +188,6 @@ const mapDispatchToProps = (dispatch) => ({ loadingFilters: (resource) => dispatch(fetchFilters(resource)), loadingServers: () => dispatch(fetchServers()), loadingServersIntoTable: () => dispatch(loadServersIntoTable()), - loadingJobs: () => dispatch(fetchJobs()), loadingJobsIntoTable: () => dispatch(loadJobsIntoTable()), loadingServices: () => dispatch(fetchServices()), loadingServicesIntoTable: () => dispatch(loadServicesIntoTable()), diff --git a/app/src/components/systems/Services.tsx b/app/src/components/systems/Services.tsx index a4172bd829..46bd300473 100644 --- a/app/src/components/systems/Services.tsx +++ b/app/src/components/systems/Services.tsx @@ -9,7 +9,6 @@ import MainNav from "../shared/MainNav"; import Notifications from "../shared/Notifications"; import { servicesTemplateMap } from "../../configs/tableConfigs/servicesTableConfig"; import { fetchFilters } from "../../thunks/tableFilterThunks"; -import { fetchJobs } from "../../thunks/jobThunks"; import { loadJobsIntoTable, loadServersIntoTable, @@ -26,6 +25,8 @@ import Footer from "../Footer"; import { getUserInformation } from "../../selectors/userInfoSelectors"; import { hasAccess } from "../../utils/utils"; import { getCurrentFilterResource } from "../../selectors/tableFilterSelectors"; +import { useAppDispatch } from "../../store"; +import { fetchJobs } from "../../slices/jobSlice"; /** * This component renders the table view of services @@ -39,8 +40,6 @@ const Services = ({ services, // @ts-expect-error TS(7031): Binding element 'loadingFilters' implicitly has an... Remove this comment to see the full error message loadingFilters, -// @ts-expect-error TS(7031): Binding element 'loadingJobs' implicitly has an 'a... Remove this comment to see the full error message - loadingJobs, // @ts-expect-error TS(7031): Binding element 'loadingJobsIntoTable' implicitly ... Remove this comment to see the full error message loadingJobsIntoTable, // @ts-expect-error TS(7031): Binding element 'loadingServers' implicitly has an... Remove this comment to see the full error message @@ -57,6 +56,7 @@ const Services = ({ currentFilterType, }) => { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const [displayNavigation, setNavigation] = useState(false); const loadServices = async () => { @@ -72,7 +72,7 @@ const Services = ({ resetOffset(); // Fetching jobs from server - loadingJobs(); + dispatch(fetchJobs()); // Load jobs into table loadingJobsIntoTable(); @@ -188,7 +188,6 @@ const mapDispatchToProps = (dispatch) => ({ loadingFilters: (resource) => dispatch(fetchFilters(resource)), loadingServices: () => dispatch(fetchServices()), loadingServicesIntoTable: () => dispatch(loadServicesIntoTable()), - loadingJobs: () => dispatch(fetchJobs()), loadingJobsIntoTable: () => dispatch(loadJobsIntoTable()), loadingServers: () => dispatch(fetchServers()), loadingServersIntoTable: () => dispatch(loadServersIntoTable()), diff --git a/app/src/reducers/jobReducer.ts b/app/src/reducers/jobReducer.ts deleted file mode 100644 index 7595db394b..0000000000 --- a/app/src/reducers/jobReducer.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { jobsTableConfig } from "../configs/tableConfigs/jobsTableConfig"; -import { - LOAD_JOBS_FAILURE, - LOAD_JOBS_IN_PROGRESS, - LOAD_JOBS_SUCCESS, - SET_JOB_COLUMNS, -} from "../actions/jobActions"; - -/** - * This file contains redux reducer for actions affecting the state of jobs - */ - -// Fill columns initially with columns defined in jobsTableConfig -const initialColumns = jobsTableConfig.columns.map((column) => ({ - ...column, - deactivated: false, -})); - -// Initial state of jobs in redux store -const initialState = { - isLoading: false, - results: [], - columns: initialColumns, - total: 0, - count: 0, - offset: 0, - limit: 0, -}; - -// Reducer for jobs -// @ts-expect-error TS(7006): Parameter 'action' implicitly has an 'any' type. -const jobs = (state = initialState, action) => { - const { type, payload } = action; - switch (type) { - case LOAD_JOBS_IN_PROGRESS: { - return { - ...state, - isLoading: true, - }; - } - case LOAD_JOBS_SUCCESS: { - const { jobs } = payload; - return { - ...state, - isLoading: false, - total: jobs.total, - count: jobs.count, - limit: jobs.limit, - offset: jobs.offset, - results: jobs.results, - }; - } - case LOAD_JOBS_FAILURE: { - return { - ...state, - isLoading: false, - }; - } - case SET_JOB_COLUMNS: { - const { updatedColumns } = payload; - return { - ...state, - columns: updatedColumns, - }; - } - default: - return state; - } -}; - -export default jobs; diff --git a/app/src/selectors/jobSelectors.ts b/app/src/selectors/jobSelectors.ts index 1d412273eb..503833a48b 100644 --- a/app/src/selectors/jobSelectors.ts +++ b/app/src/selectors/jobSelectors.ts @@ -1,6 +1,7 @@ +import { RootState } from "../store"; + /** * This file contains selectors regarding jobs */ - -export const getJobs = (state: any) => state.jobs.results; -export const getTotalJobs = (state: any) => state.jobs.total; +export const getJobs = (state: RootState) => state.jobs.results; +export const getTotalJobs = (state: RootState) => state.jobs.total; diff --git a/app/src/slices/jobSlice.ts b/app/src/slices/jobSlice.ts new file mode 100644 index 0000000000..92bdb6a546 --- /dev/null +++ b/app/src/slices/jobSlice.ts @@ -0,0 +1,102 @@ +import { PayloadAction, SerializedError, createAsyncThunk, createSlice } from '@reduxjs/toolkit' +import { jobsTableConfig } from '../configs/tableConfigs/jobsTableConfig'; +import axios from 'axios'; +import { getURLParams } from '../utils/resourceUtils'; + +/** + * This file contains redux reducer for actions affecting the state of jobs + */ +type Job = { + creator: string, + id: number, + operation: string, + processingHost: string, + processingNode: string, + started: string, + status: string, + submitted: string, + type: string, +} + +type JobState = { + status: 'uninitialized' | 'loading' | 'succeeded' | 'failed', + error: SerializedError | null, + results: Job[], + columns: any, // TODO: proper typing, derive from `initialColumns` + total: number, + count: number, + offset: number, + limit: number, +} + +// Fill columns initially with columns defined in jobsTableConfig +const initialColumns = jobsTableConfig.columns.map((column) => ({ + ...column, + deactivated: false, +})); + +// Initial state of jobs in redux store +const initialState: JobState = { + status: 'uninitialized', + error: null, + results: [], + columns: initialColumns, + total: 0, + count: 0, + offset: 0, + limit: 0, +}; + +export const fetchJobs = createAsyncThunk('jobs/fetchJobs', async (_, { getState }) => { + const state = getState(); + let params = getURLParams(state); + // Just make the async request here, and return the response. + // This will automatically dispatch a `pending` action first, + // and then `fulfilled` or `rejected` actions based on the promise. + // /jobs.json?limit=0&offset=0&filter={filter}&sort={sort} + const res = await axios.get("/admin-ng/job/jobs.json?", { params: params }); + return res.data; +}); + +const jobSlice = createSlice({ + name: 'jobs', + initialState, + reducers: { + setJobColumns(state, action: PayloadAction< + JobState["columns"] + >) { + state.columns = action.payload; + }, + }, + // These are used for thunks + extraReducers: builder => { + builder + .addCase(fetchJobs.pending, (state) => { + state.status = 'loading'; + }) + .addCase(fetchJobs.fulfilled, (state, action: PayloadAction<{ + total: JobState["total"], + count: JobState["count"], + limit: JobState["limit"], + offset: JobState["offset"], + results: JobState["results"], + }>) => { + state.status = 'succeeded'; + const jobs = action.payload; + state.total = jobs.total; + state.count = jobs.count; + state.limit = jobs.limit; + state.offset = jobs.offset; + state.results = jobs.results; + }) + .addCase(fetchJobs.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.error; + }); + } +}); + +export const { setJobColumns } = jobSlice.actions; + +// Export the slice reducer as the default export +export default jobSlice.reducer; diff --git a/app/src/store.ts b/app/src/store.ts index 5e621a954c..37df5882b5 100644 --- a/app/src/store.ts +++ b/app/src/store.ts @@ -9,7 +9,7 @@ import events from "./slices/eventSlice"; import table from "./reducers/tableReducers"; import series from "./reducers/seriesReducer"; import recordings from "./reducers/recordingReducer"; -import jobs from "./reducers/jobReducer"; +import jobs from "./slices/jobSlice"; import servers from "./reducers/serverReducer"; import services from "./reducers/serviceReducer"; import users from "./reducers/userReducers"; diff --git a/app/src/thunks/jobThunks.ts b/app/src/thunks/jobThunks.ts deleted file mode 100644 index f405d17ec7..0000000000 --- a/app/src/thunks/jobThunks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import axios from "axios"; -import { - loadJobsFailure, - loadJobsInProgress, - loadJobsSuccess, -} from "../actions/jobActions"; -import { getURLParams } from "../utils/resourceUtils"; - -// fetch jobs from server -// @ts-expect-error TS(7006): Parameter 'dispatch' implicitly has an 'any' type. -export const fetchJobs = () => async (dispatch, getState) => { - try { - dispatch(loadJobsInProgress()); - - const state = getState(); - let params = getURLParams(state); - - // /jobs.json?limit=0&offset=0&filter={filter}&sort={sort} - let data = await axios.get("/admin-ng/job/jobs.json?", { params: params }); - - const jobs = await data.data; - dispatch(loadJobsSuccess(jobs)); - } catch (e) { - console.error(e); - dispatch(loadJobsFailure()); - } -}; diff --git a/app/src/thunks/tableThunks.ts b/app/src/thunks/tableThunks.ts index 47fcd4d04c..76b13dfea7 100644 --- a/app/src/thunks/tableThunks.ts +++ b/app/src/thunks/tableThunks.ts @@ -35,14 +35,13 @@ import { } from "../selectors/tableSelectors"; import { fetchSeries } from "./seriesThunks"; import { fetchRecordings } from "./recordingThunks"; -import { fetchJobs } from "./jobThunks"; +import { fetchJobs, setJobColumns } from "../slices/jobSlice"; import { fetchServers } from "./serverThunks"; import { fetchServices } from "./serviceThunks"; import { fetchUsers } from "./userThunks"; import { fetchGroups } from "./groupThunks"; import { fetchThemes } from "./themeThunks"; import { setRecordingsColumns } from "../actions/recordingActions"; -import { setJobColumns } from "../actions/jobActions"; import { setServerColumns } from "../actions/serverActions"; import { setUserColumns } from "../actions/userActions"; import { setGroupColumns } from "../actions/groupActions";