Skip to content

Commit

Permalink
fix(dashboard): ensure fresh store state when merging data
Browse files Browse the repository at this point in the history
We were merging API responses to a stale version of the store
object. That got merged to the global store state, causing some changes
to be lost or overwritten.
  • Loading branch information
eysi09 authored and thsig committed Sep 24, 2019
1 parent ea25f30 commit bf5b5d0
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 100 deletions.
6 changes: 3 additions & 3 deletions dashboard/src/containers/entity-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ interface Props {
*/
export default ({ name, moduleName, type, onClose }: Props) => {
const {
actions: { loadTestResult, loadTaskResult },
actions,
store: { entities: { tasks, tests }, requestStates: { fetchTestResult, fetchTaskResult } },
} = useApi()

const loadResults = () => {
if (type === "test") {
loadTestResult({ name, moduleName, force: true })
actions.loadTestResult({ name, moduleName, force: true })
} else if (type === "run" || type === "task") {
loadTaskResult({ name, force: true })
actions.loadTaskResult({ name, force: true })
}
}

Expand Down
18 changes: 9 additions & 9 deletions dashboard/src/containers/graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,34 +36,34 @@ export interface GraphOutputWithNodeStatus extends GraphOutput {

export default () => {
const {
actions: { loadGraph, loadConfig },
actions,
store: { entities: { modules, services, tests, tasks, graph }, requestStates: { fetchGraph, fetchTaskStates } },
} = useApi()

const {
actions: { selectGraphNode, stackGraphToggleItemsView, clearGraphNodeSelection },
state: { selectedGraphNode, isSidebarOpen, stackGraph: { filters } },
} = useUiState()

useEffect(() => {
async function fetchData() {
return await loadConfig()
return await actions.loadConfig()
}
fetchData()
}, [])

useEffect(() => {
async function fetchData() {
return await loadGraph()
return await actions.loadGraph()
}
fetchData()
}, [])

const {
actions: { selectGraphNode, stackGraphToggleItemsView, clearGraphNodeSelection },
state: { selectedGraphNode, isSidebarOpen, stackGraph: { filters } },
} = useUiState()

if (fetchGraph.error) {
return <PageError error={fetchGraph.error} />
}

if (fetchGraph.loading) {
if (!fetchGraph.initLoadComplete) {
return <Spinner />
}

Expand Down
12 changes: 6 additions & 6 deletions dashboard/src/containers/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,30 @@ import Spinner from "../components/spinner"

export default () => {
const {
actions: { loadConfig, loadLogs },
actions,
store: { entities: { logs, services }, requestStates: { fetchLogs, fetchConfig } },
} = useApi()

const serviceNames: string[] = Object.keys(services)

useEffect(() => {
async function fetchData() {
return await loadConfig()
return await actions.loadConfig()
}
fetchData()
}, [])

useEffect(() => {
async function fetchData() {
return await loadLogs({ serviceNames })
return await actions.loadLogs({ serviceNames })
}

if (serviceNames.length) {
fetchData()
}
}, [fetchConfig.didFetch]) // run again only after config had been fetched
}, [fetchConfig.initLoadComplete]) // run again only after config had been fetched

if (fetchConfig.loading || fetchLogs.loading) {
if (!(fetchConfig.initLoadComplete && fetchLogs.initLoadComplete)) {
return <Spinner />
}

Expand All @@ -49,6 +49,6 @@ export default () => {
}

return (
<Logs onRefresh={loadLogs} logs={logs} />
<Logs onRefresh={actions.loadLogs} logs={logs} />
)
}
18 changes: 6 additions & 12 deletions dashboard/src/containers/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ const mapTasks = (taskEntities: Task[], moduleName: string): ModuleProps["taskCa

export default () => {
const {
actions,
store: {
projectRoot,
entities: { modules, services, tests, tasks },
requestStates: { fetchConfig, fetchStatus },
},
actions: { loadConfig, loadStatus },
} = useApi()

const {
Expand All @@ -94,18 +94,9 @@ export default () => {
},
} = useUiState()

// TODO use useAsyncEffect?
// https://dev.to/n1ru4l/homebrew-react-hooks-useasynceffect-or-how-to-handle-async-operations-with-useeffect-1fa8
useEffect(() => {
async function fetchData() {
return await loadConfig()
}
fetchData()
}, [])

useEffect(() => {
async function fetchData() {
return await loadStatus()
return await actions.loadConfig()
}
fetchData()
}, [])
Expand All @@ -118,7 +109,10 @@ export default () => {
return <PageError error={fetchConfig.error || fetchStatus.error} />
}

if (fetchConfig.loading || fetchStatus.loading) {
// Note that we don't call the loadStatus function here since the Sidebar ensures that the status is always loaded.
// FIXME: We should be able to call loadStatus safely and have the handler check if the status
// has already been fetched or is pending.
if (!(fetchConfig.initLoadComplete && fetchStatus.initLoadComplete)) {
return <Spinner />
}

Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/containers/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ const builtinPages: Page[] = [

const SidebarContainer = () => {
const {
actions: { loadStatus },
actions,
store: { entities: { providers } },
} = useApi()

useEffect(() => {
async function fetchData() {
return await loadStatus()
return await actions.loadStatus()
}
fetchData()
}, [])
Expand Down
45 changes: 26 additions & 19 deletions dashboard/src/contexts/api-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ interface LoadHandlerParams {
export async function loadConfigHandler({ store, dispatch, force = false }: LoadHandlerParams) {
const requestKey = "fetchConfig"

if (!force && store.requestStates[requestKey].didFetch) {
if (!force && store.requestStates[requestKey].initLoadComplete) {
return
}

Expand All @@ -66,8 +66,9 @@ export async function loadConfigHandler({ store, dispatch, force = false }: Load
return
}

const processedStore = processConfig(store, res)
dispatch({ store: processedStore, type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processConfig(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processConfig(store: Store, config: ConfigDump) {
Expand Down Expand Up @@ -111,23 +112,21 @@ function processConfig(store: Store, config: ConfigDump) {
}
}

const processedStore = produce(store, storeDraft => {
return produce(store, storeDraft => {
storeDraft.entities.modules = modules
storeDraft.entities.services = services
storeDraft.entities.tests = tests
storeDraft.entities.tasks = tasks
storeDraft.projectRoot = config.projectRoot
})

return processedStore
}

interface LoadLogsHandlerParams extends LoadHandlerParams, FetchLogsParams { }

export async function loadLogsHandler({ serviceNames, store, dispatch, force = false }: LoadLogsHandlerParams) {
const requestKey = "fetchLogs"

if ((!force && store.requestStates[requestKey].didFetch) || !serviceNames.length) {
if ((!force && store.requestStates[requestKey].initLoadComplete) || !serviceNames.length) {
return
}
dispatch({ requestKey, type: "fetchStart" })
Expand All @@ -140,7 +139,9 @@ export async function loadLogsHandler({ serviceNames, store, dispatch, force = f
return
}

dispatch({ store: processLogs(store, res), type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processLogs(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processLogs(store: Store, logs: ServiceLogEntry[]) {
Expand All @@ -152,7 +153,7 @@ function processLogs(store: Store, logs: ServiceLogEntry[]) {
export async function loadStatusHandler({ store, dispatch, force = false }: LoadHandlerParams) {
const requestKey = "fetchStatus"

if (!force && store.requestStates[requestKey].didFetch) {
if (!force && store.requestStates[requestKey].initLoadComplete) {
return
}

Expand All @@ -166,11 +167,13 @@ export async function loadStatusHandler({ store, dispatch, force = false }: Load
return
}

dispatch({ store: processStatus(store, res), type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processStatus(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processStatus(store: Store, status: StatusCommandResult) {
const processedStore = produce(store, storeDraft => {
return produce(store, storeDraft => {
for (const serviceName of Object.keys(status.services)) {
storeDraft.entities.services[serviceName] = {
...storeDraft.entities.services[serviceName],
Expand All @@ -191,8 +194,6 @@ function processStatus(store: Store, status: StatusCommandResult) {
}
storeDraft.entities.providers = status.providers
})

return processedStore
}

interface LoadTaskResultHandlerParams extends LoadHandlerParams, FetchTaskResultParams { }
Expand All @@ -202,7 +203,7 @@ export async function loadTaskResultHandler(
) {
const requestKey = "fetchTaskResult"

if (!force && store.requestStates[requestKey].didFetch) {
if (!force && store.requestStates[requestKey].initLoadComplete) {
return
}

Expand All @@ -216,7 +217,9 @@ export async function loadTaskResultHandler(
return
}

dispatch({ store: processTaskResult(store, res), type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processTaskResult(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processTaskResult(store: Store, result: TaskResultOutput) {
Expand All @@ -232,7 +235,7 @@ interface LoadTestResultParams extends LoadHandlerParams, FetchTestResultParams
export async function loadTestResultHandler({ store, dispatch, force = false, ...fetchParams }: LoadTestResultParams) {
const requestKey = "fetchTestResult"

if (!force && store.requestStates[requestKey].didFetch) {
if (!force && store.requestStates[requestKey].initLoadComplete) {
return
}

Expand All @@ -246,7 +249,9 @@ export async function loadTestResultHandler({ store, dispatch, force = false, ..
return
}

dispatch({ store: processTestResult(store, res), type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processTestResult(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processTestResult(store: Store, result: TestResultOutput) {
Expand All @@ -260,7 +265,7 @@ function processTestResult(store: Store, result: TestResultOutput) {
export async function loadGraphHandler({ store, dispatch, force = false }: LoadHandlerParams) {
const requestKey = "fetchGraph"

if (!force && store.requestStates[requestKey].didFetch) {
if (!force && store.requestStates[requestKey].initLoadComplete) {
return
}

Expand All @@ -274,7 +279,9 @@ export async function loadGraphHandler({ store, dispatch, force = false }: LoadH
return
}

dispatch({ store: processGraph(store, res), type: "fetchSuccess", requestKey })
const produceNextStore = (currentStore: Store) => processGraph(currentStore, res)

dispatch({ type: "fetchSuccess", requestKey, produceNextStore })
}

function processGraph(store: Store, graph: GraphOutput) {
Expand Down
Loading

0 comments on commit bf5b5d0

Please sign in to comment.