Repository navigation
feat: [2312] Dashboard data store - Read from the store and remove the legacy datastore #2316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f5967f8
9dfe94e
23c12e6
684af5e
c7c870b
8e0fba9
d3605d4
9a0ce0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,21 @@ | ||
| const { isClientScoped } = require('../utils/index.js') | ||
|
|
||
| const { attachToContext, MERGE_ENTRY, APPEND_ENTRY, SET_SERIES, TRIM_SERIES } = require('./reactive.js') | ||
|
|
||
| const data = {} | ||
| const { createDataStore, attachToContext, MERGE_ENTRY, APPEND_ENTRY, SET_SERIES, TRIM_SERIES, ENTRY_MSG } = require('./reactive.js') | ||
|
|
||
| const config = { | ||
| RED: null | ||
| } | ||
|
|
||
| let storeOptions = {} | ||
| let storeEnabled = false | ||
| let fallbackStore = null | ||
| let storeContext = null | ||
| let storeBase = null | ||
| let warned = false | ||
|
|
||
| function warnOnce (node, problem) { | ||
| if (warned || !node) return | ||
| function warnOnce (problem) { | ||
| if (warned || !storeBase) return | ||
| warned = true | ||
| node.warn(`Dashboard data store: ${problem}. Further store failures are not reported until the next full deploy or restart.`) | ||
| storeBase.warn(`Dashboard data store: ${problem}. Further store failures are not reported until the next full deploy or restart.`) | ||
| } | ||
|
|
||
| const attachOptions = () => ({ clone: config.RED.util.cloneMessage, ...storeOptions }) | ||
|
|
@@ -29,48 +27,28 @@ function getOrCreateStore (globalContext) { | |
| return store | ||
| } | ||
|
|
||
| function getStoreOrWarn (action, id) { | ||
| try { | ||
| return fallbackStore || getOrCreateStore(storeContext) | ||
| } catch (err) { warnOnce(`${action} ${id} failed (${err.message})`) } | ||
| } | ||
|
|
||
| function initStore (globalContext, { node, ...opts } = {}) { | ||
| storeOptions = opts | ||
| storeEnabled = true | ||
| storeContext = globalContext | ||
| storeBase = node | ||
| warned = false | ||
| return attachToContext(globalContext, attachOptions()) | ||
| } | ||
|
|
||
| function disableStore () { | ||
| storeEnabled = false | ||
| } | ||
|
|
||
| function writeToStore (node, msg) { | ||
| if (!storeEnabled) return | ||
| if (!msg || typeof msg !== 'object') return | ||
| try { | ||
| getOrCreateStore(node.context().global)[MERGE_ENTRY](node.id, msg) | ||
| } catch (err) { warnOnce(node, `a store write failed (${err.message}), so global.dashboardStore may be out of date`) } | ||
| } | ||
|
|
||
| function appendToStore (node, msg) { | ||
| if (!storeEnabled) return | ||
| if (!msg || typeof msg !== 'object') return | ||
| try { | ||
| getOrCreateStore(node.context().global)[APPEND_ENTRY](node.id, msg) | ||
| } catch (err) { warnOnce(node, `a store write failed (${err.message}), so global.dashboardStore may be out of date`) } | ||
| } | ||
|
|
||
| function replaceInStore (node, msgs) { | ||
| if (!storeEnabled) return | ||
| if (!Array.isArray(msgs) || msgs.some((m) => !m || typeof m !== 'object')) return | ||
| try { | ||
| getOrCreateStore(node.context().global)[SET_SERIES](node.id, msgs) | ||
| } catch (err) { warnOnce(node, `a store write failed (${err.message}), so global.dashboardStore may be out of date`) } | ||
| } | ||
|
|
||
| function trimInStore (node, trim) { | ||
| if (!storeEnabled) return | ||
| let store = null | ||
| try { | ||
| getOrCreateStore(node.context().global)[TRIM_SERIES](node.id, trim) | ||
| } catch (err) { warnOnce(node, `a store write failed (${err.message}), so global.dashboardStore may be out of date`) } | ||
| store = attachToContext(globalContext, attachOptions()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noticed something with a persistent context store (localfilesystem, cache: true). The store only gets written to disk when something else in global context is set, and then only the values go. After a restart my text input came back with its old value, which main never did, and the chart read back as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yahh makes sense, went with starting fresh like main for now, which also fixes the chart coming back as #2322 also keeps a chart's messages in step with its points for flow writes. I do want state to eventually survive restarts, but it can be settled in the persistence work #2237 |
||
| } finally { | ||
| if (store) { | ||
| fallbackStore = null | ||
| } else if (!fallbackStore) { | ||
| fallbackStore = createDataStore(attachOptions()) | ||
| } | ||
| } | ||
| return store | ||
| } | ||
|
|
||
| function latestPerTopic (msgs, maxPoints) { | ||
|
|
@@ -120,26 +98,17 @@ function canSaveInStore (base, node, msg) { | |
| return checks.length === 0 || !checks.includes(false) | ||
| } | ||
|
|
||
| // Strip msg of properties that are not needed for storage | ||
| function stripMsg (msg) { | ||
| const newMsg = config.RED.util.cloneMessage(msg) | ||
|
|
||
| // don't need to store ui_updates in the datastore, as this is handled in statestore | ||
| delete newMsg.ui_update | ||
|
|
||
| return newMsg | ||
| } | ||
|
|
||
| const getters = { | ||
| RED () { | ||
| return config.RED | ||
| }, | ||
| // given a widget id, return the latest msg received | ||
| msg (id) { | ||
| return config.RED.util.cloneMessage(data[id]) | ||
| return config.RED.util.cloneMessage(getStoreOrWarn('reading', id)?.[ENTRY_MSG](id)) | ||
| }, | ||
| has (id) { | ||
| return !!data[id] | ||
| const store = getStoreOrWarn('reading', id) | ||
| return !!store && id in store | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -150,13 +119,10 @@ const setters = { | |
| }, | ||
| // remove data associated to a given widget | ||
| clear (id) { | ||
| delete data[id] | ||
| if (!storeEnabled) { | ||
| return | ||
| const store = getStoreOrWarn('clearing', id) | ||
| if (store) { | ||
| delete store[id] | ||
| } | ||
| try { | ||
| delete getOrCreateStore(storeContext)[id] | ||
| } catch (err) { warnOnce(storeBase, `clearing the store entry for ${id} failed (${err.message}), so global.dashboardStore may still hold its old value`) } | ||
| }, | ||
| /** | ||
| * | ||
|
|
@@ -166,35 +132,20 @@ const setters = { | |
| */ | ||
| save (base, node, msg) { | ||
| if (Array.isArray(msg)) { | ||
| /// need to check msg by msg | ||
| const filtered = [] | ||
| for (const m of msg) { | ||
| if (canSaveInStore(base, node, m)) { | ||
| filtered.push(config.RED.util.cloneMessage(m)) | ||
| } | ||
| } | ||
| data[node.id] = filtered | ||
| replaceInStore(node, filtered) | ||
| } else { | ||
| if (canSaveInStore(base, node, msg)) { | ||
| const newMsg = stripMsg(msg) | ||
| data[node.id] = { | ||
| ...data[node.id], | ||
| ...newMsg | ||
| } | ||
| writeToStore(node, newMsg) | ||
| const msgs = msg.filter((m) => canSaveInStore(base, node, m)) | ||
| if (msgs.every((m) => m && typeof m === 'object')) { | ||
| getStoreOrWarn('saving', node.id)?.[SET_SERIES](node.id, msgs) | ||
| } | ||
| } else if (msg && typeof msg === 'object' && canSaveInStore(base, node, msg)) { | ||
| const { ui_update: uiUpdate, ...rest } = msg | ||
| getStoreOrWarn('saving', node.id)?.[MERGE_ENTRY](node.id, rest) | ||
| } | ||
| }, | ||
| // given a widget id, and msg, store in an array of history of values | ||
| // useful for charting widgets | ||
| append (base, node, msg) { | ||
| if (canSaveInStore(base, node, msg)) { | ||
| if (!data[node.id]) { | ||
| data[node.id] = [] | ||
| } | ||
| data[node.id].push(config.RED.util.cloneMessage(msg)) | ||
| appendToStore(node, msg) | ||
| if (msg && typeof msg === 'object' && canSaveInStore(base, node, msg)) { | ||
| getStoreOrWarn('saving', node.id)?.[APPEND_ENTRY](node.id, msg) | ||
| } | ||
| }, | ||
| /** | ||
|
|
@@ -204,28 +155,12 @@ const setters = { | |
| * @param {(msg) => Boolean} filterFunction | ||
| */ | ||
| filter (base, node, filterFunction) { | ||
| const currentData = data[node.id] | ||
| if (filterFunction && Array.isArray(currentData) && currentData.length) { | ||
| const filteredMessages = currentData.filter(filterFunction) | ||
| if (filteredMessages.length !== currentData.length) { | ||
| // no need for save operation to process messages - just apply them | ||
| data[node.id] = filteredMessages | ||
| } | ||
| } | ||
| if (filterFunction) { | ||
| trimInStore(node, (msgs) => msgs.filter(filterFunction)) | ||
| getStoreOrWarn('trimming', node.id)?.[TRIM_SERIES](node.id, (msgs) => msgs.filter(filterFunction)) | ||
| } | ||
| }, | ||
| keepLatestPerTopic (base, node, maxPoints) { | ||
| trimInStore(node, (msgs) => latestPerTopic(msgs, maxPoints)) | ||
| const currentData = data[node.id] | ||
| if (!Array.isArray(currentData)) { | ||
| return | ||
| } | ||
| const kept = latestPerTopic(currentData, maxPoints) | ||
| if (kept.length !== currentData.length) { | ||
| data[node.id] = kept | ||
| } | ||
| getStoreOrWarn('trimming', node.id)?.[TRIM_SERIES](node.id, (msgs) => latestPerTopic(msgs, maxPoints)) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -239,6 +174,5 @@ module.exports = { | |
| filter: setters.filter, | ||
| keepLatestPerTopic: setters.keepLatestPerTopic, | ||
| clear: setters.clear, | ||
| initStore, | ||
| disableStore | ||
| initStore | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works nicely. I tried cache: false and the dashboard kept its data through a full redeploy. Minor: the warning shows on every full deploy, which might get a bit noisy for those users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good call fixed