front/context.js
import { List, Map } from 'immutable'
import React, { useEffect, useReducer, useState } from 'react'
import { Dataset } from './models/dataset'
import { Experiment } from './models/experiment'
import { Project } from './models/project'
import { STATUS_API_CALL_INTERVAL } from './constants'
import { Status } from './models/status'
import { toast } from 'react-toastify'
export const GlobalContext = React.createContext({})
const showErrorToast = (message) => {
toast.error(message, {
position: 'bottom-center',
autoClose: 5000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: false
})
}
const showNetworkErrorToast = (err) => {
const { url, method } = err.config
let message = `${method.toUpperCase()}: ${url} "${err.message}"`
if (err.response) {
const errorMessage = JSON.stringify(err.response.data)
const { status } = err.response
message = `${method.toUpperCase()}: ${url} "${status}: ${errorMessage}"`
}
showErrorToast(message)
}
const updateFunc = (data, setData) => (datum) => {
const index = data.findIndex((d) => d.id === datum.id)
setData(data.set(index, datum))
return datum.update().catch((err) => showNetworkErrorToast(err))
}
const deleteFunc = (data, setData) => (datum) => {
const newData = data.filter((d) => d.id !== datum.id)
setData(newData)
return datum.delete().catch((err) => showNetworkErrorToast(err))
}
// Reducers
const experimentReducer = (experiments, action) => {
const actions = {
fetch: () => experiments.set(action.projectId, List(action.experiments)),
create: () => {
if (experiments.has(action.projectId)) {
const target = experiments.get(action.projectId)
const newExperiments = target.unshift(action.experiment)
return experiments.set(action.projectId, newExperiments)
}
return experiments.set(action.projectId, List([action.experiment]))
},
update: () => {
const { experiment } = action
const target = experiments.get(action.projectId)
const index = target.findIndex((ex) => ex.id === experiment.id)
const newExperiments = target.set(index, experiment)
return experiments.set(action.projectId, newExperiments)
},
delete: () => {
const { experiment } = action
const target = experiments.get(action.projectId)
const newExperiments = target.filter((ex) => ex.id !== experiment.id)
return experiments.set(action.projectId, newExperiments)
}
}
return actions[action.type]()
}
export const GlobalProvider = ({ children }) => {
const [datasets, setDatasets] = useState(List([]))
const [projects, setProjects] = useState(List([]))
const [experiments, dispatch] = useReducer(experimentReducer, Map({}))
const [examples, setExamples] = useState(Map({}))
const [status, setStatus] = useState({})
const [statusTime, setStatusTime] = useState(Date.now())
// Initialization
useEffect(() => {
Dataset.getAll()
.then((newDatasets) => setDatasets(List(newDatasets)))
.catch((err) => showNetworkErrorToast(err))
Project.getAll()
.then((newProjects) => setProjects(List(newProjects)))
.catch((err) => showNetworkErrorToast(err))
Status.get()
.then((newStatus) => setStatus(newStatus))
.catch((err) => showNetworkErrorToast(err))
}, [])
// Periodical API calls
useEffect(() => {
const timeoutId = setTimeout(() => {
setStatusTime(Date.now())
Status.get()
.then((newStatus) => setStatus(newStatus))
.catch((err) => showNetworkErrorToast(err))
}, STATUS_API_CALL_INTERVAL)
return () => {
clearTimeout(timeoutId)
}
}, [statusTime])
// Actions
const uploadDataset = (file, isImage, zipFile, progressCallback) => (
Dataset.upload(file, isImage, zipFile, progressCallback)
.then((dataset) => {
setDatasets(datasets.unshift(dataset))
return dataset
})
.catch((err) => showNetworkErrorToast(err))
)
const deleteDataset = deleteFunc(datasets, setDatasets)
const updateDataset = updateFunc(datasets, setDatasets)
const createProject = (datasetId, name, algorithm, progressCallback) => (
Project.create(datasetId, name, algorithm, progressCallback)
.then((project) => {
setProjects(projects.unshift(project))
return project
})
.catch((err) => showNetworkErrorToast(err))
)
const deleteProject = deleteFunc(projects, setProjects)
const updateProject = updateFunc(projects, setProjects)
const fetchExperiments = (projectId) => (
Experiment.getAll(projectId)
.then((newExperiments) => {
dispatch({
type: 'fetch',
projectId: projectId,
experiments: newExperiments
})
return newExperiments
})
.catch((err) => showNetworkErrorToast(err))
)
const createExperiment = (projectId, name, config, progressCallback) => (
Experiment.create(projectId, name, config, progressCallback)
.then((experiment) => {
dispatch({
type: 'create',
projectId: projectId,
experiment: experiment
})
return experiment
})
.catch((err) => showNetworkErrorToast(err))
)
const deleteExperiment = (experiment) => {
dispatch({
type: 'delete',
projectId: experiment.projectId,
experiment: experiment
})
return experiment.delete().catch((err) => showNetworkErrorToast(err))
}
const updateExperiment = (experiment) => {
dispatch({
type: 'update',
projectId: experiment.projectId,
experiment: experiment
})
return experiment.update().catch((err) => showNetworkErrorToast(err))
}
const cancelExperiment = (experiment) => {
dispatch({
type: 'update',
projectId: experiment.projectId,
experiment: experiment.set('isActive', false)
})
return experiment.cancel().catch((err) => showNetworkErrorToast(err))
}
const fetchExampleObservations = (dataset) => {
dataset.getExampleObservations()
.then((observations) => {
const newExamples = examples.set(dataset.id, observations)
setExamples(newExamples)
return observations
})
.catch((err) => showErrorToast(err))
}
return (
<GlobalContext.Provider
value={{
datasets,
projects,
experiments,
examples,
status,
uploadDataset,
deleteDataset,
updateDataset,
createProject,
updateProject,
deleteProject,
fetchExperiments,
createExperiment,
deleteExperiment,
updateExperiment,
cancelExperiment,
fetchExampleObservations,
showErrorToast,
showNetworkErrorToast
}}
>
{children}
</GlobalContext.Provider>
)
}