From 9921f2d5bae7679d78ba306b9b48720ffe4b2f54 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:30:14 +0100 Subject: [PATCH 01/16] Add RED.popover.dialog api --- .../editor-client/src/js/ui/common/popover.js | 68 +++++++++++++++++++ .../editor-client/src/sass/popover.scss | 36 ++++++++++ 2 files changed, 104 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js index 381bb9d3a..308f25f0e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js @@ -673,6 +673,74 @@ RED.popover = (function() { show:show, hide:hide } + }, + dialog: function(options) { + + const dialogContent = $('
'); + + if (options.closeButton !== false) { + $('').appendTo(dialogContent).click(function(evt) { + evt.preventDefault(); + close(); + }) + } + + const dialogBody = $('
').appendTo(dialogContent); + if (options.title) { + $('

').text(options.title).appendTo(dialogBody); + } + $('
').css("text-align","left").html(options.content).appendTo(dialogBody); + + const stepToolbar = $('
',{class:"red-ui-dialog-toolbar"}).appendTo(dialogContent); + + if (options.buttons) { + options.buttons.forEach(button => { + const btn = $('').text(button.text).appendTo(stepToolbar); + if (button.class) { + btn.addClass(button.class); + } + if (button.click) { + btn.on('click', function(evt) { + evt.preventDefault(); + button.click(); + }) + } + + }) + } + + const width = 500; + const maxWidth = Math.min($(window).width()-10,Math.max(width || 0, 300)); + + let shade = $('
').appendTo(document.body); + shade.fadeIn() + + let popover = RED.popover.create({ + target: $(".red-ui-editor"), + width: width || "auto", + maxWidth: maxWidth+"px", + direction: "inset", + class: "red-ui-dialog", + trigger: "manual", + content: dialogContent + }).open() + + function close() { + if (shade) { + shade.fadeOut(() => { + shade.remove() + shade = null + }) + } + if (popover) { + popover.close() + popover = null + } + } + + return { + close + } } } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss index 3df2b495b..027e783a3 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss @@ -205,3 +205,39 @@ background: var(--red-ui-secondary-background); z-index: 2000; } + + +.red-ui-popover.red-ui-dialog { + z-index: 2003; + --red-ui-popover-background: var(--red-ui-secondary-background); + --red-ui-popover-border: var(--red-ui-tourGuide-border); + --red-ui-popover-color: var(--red-ui-primary-text-color); + + .red-ui-popover-content { + h2 { + text-align: center; + margin-top: 0px; + line-height: 1.2em; + color: var(--red-ui-tourGuide-heading-color); + i.fa { + font-size: 1.5em + } + } + } + +} + +.red-ui-dialog-toolbar { + min-height: 36px; + position: relative; + display: flex; + justify-content: flex-end; + gap: 10px; +} +.red-ui-dialog-body { + padding: 20px 40px 10px; + a { + color: var(--red-ui-text-color-link) !important; + text-decoration: none; + } +} From 9a784191baf507738dcdf83ab34e5073dc4d2a52 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:31:46 +0100 Subject: [PATCH 02/16] Add runtime telemetry component --- .../@node-red/runtime/lib/api/settings.js | 14 ++ .../@node-red/runtime/lib/index.js | 3 + .../@node-red/runtime/lib/telemetry/index.js | 189 ++++++++++++++++++ .../runtime/lib/telemetry/metrics/01-core.js | 5 + .../runtime/lib/telemetry/metrics/02-os.js | 9 + .../runtime/lib/telemetry/metrics/03-env.js | 8 + .../@node-red/runtime/package.json | 4 +- packages/node_modules/node-red/red.js | 8 +- 8 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 packages/node_modules/@node-red/runtime/lib/telemetry/index.js create mode 100644 packages/node_modules/@node-red/runtime/lib/telemetry/metrics/01-core.js create mode 100644 packages/node_modules/@node-red/runtime/lib/telemetry/metrics/02-os.js create mode 100644 packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 1aa335f1a..634f5dbf3 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -161,6 +161,8 @@ var api = module.exports = { safeSettings.diagnostics.ui = false; // cannot have UI without endpoint } + safeSettings.telemetryEnabled = runtime.telemetry.isEnabled() + safeSettings.runtimeState = { //unless runtimeState.ui and runtimeState.enabled are explicitly true, they will default to false. enabled: !!runtime.settings.runtimeState && runtime.settings.runtimeState.enabled === true, @@ -213,7 +215,19 @@ var api = module.exports = { } var currentSettings = runtime.settings.getUserSettings(username)||{}; currentSettings = extend(currentSettings, opts.settings); + try { + if (currentSettings.hasOwnProperty("telemetryEnabled")) { + // This is a global setting that is being set by the user. It should + // not be stored per-user as it applies to the whole runtime. + const telemetryEnabled = currentSettings.telemetryEnabled; + delete currentSettings.telemetryEnabled; + if (telemetryEnabled) { + runtime.telemetry.enable() + } else { + runtime.telemetry.disable() + } + } return runtime.settings.setUserSettings(username, currentSettings).then(function() { runtime.log.audit({event: "settings.update",username:username}, opts.req); return; diff --git a/packages/node_modules/@node-red/runtime/lib/index.js b/packages/node_modules/@node-red/runtime/lib/index.js index 4ac7cfb5b..3251ff2fa 100644 --- a/packages/node_modules/@node-red/runtime/lib/index.js +++ b/packages/node_modules/@node-red/runtime/lib/index.js @@ -23,6 +23,7 @@ var library = require("./library"); var plugins = require("./plugins"); var settings = require("./settings"); const multiplayer = require("./multiplayer"); +const telemetry = require("./telemetry"); var express = require("express"); var path = require('path'); @@ -135,6 +136,7 @@ function start() { return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json") .then(function() { return storage.init(runtime)}) .then(function() { return settings.load(storage)}) + .then(function() { return telemetry.init(runtime)}) .then(function() { return library.init(runtime)}) .then(function() { return multiplayer.init(runtime)}) .then(function() { @@ -337,6 +339,7 @@ var runtime = { library: library, exec: exec, util: util, + telemetry: telemetry, get adminApi() { return adminApi }, get adminApp() { return adminApp }, get nodeApp() { return nodeApp }, diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js new file mode 100644 index 000000000..fef1cc1db --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -0,0 +1,189 @@ +const path = require('path') +const fs = require('fs/promises') +const semver = require('semver') +const cronosjs = require('cronosjs') + +const METRICS_DIR = path.join(__dirname, 'metrics') +const INITIAL_PING_DELAY = 1000 * 60 * 30 // 30 minutes from startup + +let runtime + +let scheduleTask + +async function gather () { + let metricFiles = await fs.readdir(METRICS_DIR) + metricFiles = metricFiles.filter(name => /^\d+-.*\.js$/.test(name)) + metricFiles.sort() + + const metrics = {} + + for (let i = 0, l = metricFiles.length; i < l; i++) { + const metricModule = require(path.join(METRICS_DIR, metricFiles[i])) + let result = metricModule(runtime) + if (!!result && (typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') { + result = await result + } + const keys = Object.keys(result) + keys.forEach(key => { + const keyParts = key.split('.') + let p = metrics + keyParts.forEach((part, index) => { + if (index < keyParts.length - 1) { + if (!p[part]) { + p[part] = {} + } + p = p[part] + } else { + p[part] = result[key] + } + }) + }) + } + return metrics +} + +async function report () { + if (!isTelemetryEnabled()) { + return + } + // If enabled, gather metrics + const metrics = await gather() + console.log(JSON.stringify(metrics, null, 2)) + + // Post metrics to endpoint - handle any error silently + + const { got } = await import('got') + runtime.log.debug('Sending telemetry') + const response = await got.post('https://telemetry.nodered.org/ping', { + json: metrics, + responseType: 'json', + headers: { + 'User-Agent': `Node-RED/${runtime.settings.version}` + } + }).json().catch(err => { + // swallow errors + runtime.log.debug('Failed to send telemetry: ' + err.toString()) + }) + // Example response: + // { 'node-red': { latest: '4.0.9', next: '4.1.0-beta.1.9' } } + runtime.log.debug(`Telemetry response: ${JSON.stringify(response)}`) + // Get response from endpoint + if (response?.['node-red']) { + const currentVersion = metrics.env['node-red'] + if (semver.valid(currentVersion)) { + const latest = response['node-red'].latest + const next = response['node-red'].next + let updatePayload + if (semver.lt(currentVersion, latest)) { + // Case one: current < latest + runtime.log.info(`A new version of Node-RED is available: ${latest}`) + updatePayload = { version: latest } + } else if (semver.gt(currentVersion, latest) && semver.lt(currentVersion, next)) { + // Case two: current > latest && current < next + runtime.log.info(`A new beta version of Node-RED is available: ${next}`) + updatePayload = { version: next } + } + + if (updatePayload && isUpdateNotificationEnabled()) { + runtime.events.emit("runtime-event",{id:"update-available", payload: updatePayload, retain: true}); + } + } + } +} + +function isTelemetryEnabled () { + // If NODE_RED_DISABLE_TELEMETRY was set, or --no-telemetry was specified, + // the settings object will have been updated to disable telemetry explicitly + + // If there are no telemetry settings then the user has not had a chance + // to opt out yet - so keep it disabled until they do + + const telemetrySettings = runtime.settings.get('telemetry') + const runtimeTelemetryEnabled = runtime.settings.get('telemetryEnabled') + + if (telemetrySettings === undefined && runtimeTelemetryEnabled === undefined) { + // No telemetry settings - so keep it disabled + return undefined + } + + // If there are telemetry settings, use what it says + if (telemetrySettings && telemetrySettings.enabled !== undefined) { + return telemetrySettings.enabled + } + + // User has made a choice; defer to that + if (runtimeTelemetryEnabled !== undefined) { + return runtimeTelemetryEnabled + } + + // At this point, we have no sign the user has consented to telemetry, so + // keep disabled - but return undefined as a false-like value to distinguish + // it from the explicit disable above + return undefined +} + +function isUpdateNotificationEnabled () { + const telemetrySettings = runtime.settings.get('telemetry') || {} + return telemetrySettings.updateNotification !== false +} +/** + * Start the telemetry schedule + */ +function startTelemetry () { + if (scheduleTask) { + // Already scheduled - nothing left to do + return + } + + const pingTime = new Date(Date.now() + INITIAL_PING_DELAY) + const pingMinutes = '*'//pingTime.getMinutes() + const pingHours = '*'//pingTime.getHours() + const pingSchedule = `${pingMinutes} ${pingHours} * * *` + runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`) + + scheduleTask = cronosjs.scheduleTask(pingSchedule, () => { + report() + }) +} + +function stopTelemetry () { + if (scheduleTask) { + runtime.log.debug(`Telemetry disabled`) + scheduleTask.stop() + scheduleTask = null + } +} + +module.exports = { + init: (_runtime) => { + runtime = _runtime + if (isTelemetryEnabled()) { + startTelemetry() + } + }, + /** + * Enable telemetry via user opt-in in the editor + */ + enable: () => { + if (runtime.settings.available()) { + runtime.settings.set('telemetryEnabled', true) + } + startTelemetry() + }, + + /** + * Disable telemetry via user opt-in in the editor + */ + disable: () => { + if (runtime.settings.available()) { + runtime.settings.set('telemetryEnabled', false) + } + stopTelemetry() + }, + + /** + * Get telemetry enabled status + * @returns {boolean} true if telemetry is enabled, false if disabled, undefined if not set + */ + isEnabled: isTelemetryEnabled +} \ No newline at end of file diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/01-core.js b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/01-core.js new file mode 100644 index 000000000..acac829fb --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/01-core.js @@ -0,0 +1,5 @@ +module.exports = (runtime) => { + return { + instanceId: runtime.settings.get('instanceId') + } +} diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/02-os.js b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/02-os.js new file mode 100644 index 000000000..ae2a31859 --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/02-os.js @@ -0,0 +1,9 @@ +const os = require('os') + +module.exports = (_) => { + return { + 'os.type': os.type(), + 'os.release': os.release(), + 'os.arch': os.arch() + } +} diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js new file mode 100644 index 000000000..812c3ee4e --- /dev/null +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js @@ -0,0 +1,8 @@ +const process = require('process') + +module.exports = async (runtime) => { + return { + 'env.nodejs': process.version.replace(/^v/, ''), + 'env.node-red': runtime.settings.version + } +} diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index e6f1ca0d0..03ef360d2 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -20,9 +20,11 @@ "@node-red/util": "4.1.0-beta.0", "async-mutex": "0.5.0", "clone": "2.1.2", + "cronosjs": "1.7.1", "express": "4.21.2", "fs-extra": "11.3.0", "json-stringify-safe": "5.0.1", - "rfdc": "^1.3.1" + "rfdc": "^1.3.1", + "semver": "7.7.1" } } diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 5f3c9da25..d98b69f8f 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -63,7 +63,8 @@ var knownOpts = { "verbose": Boolean, "safe": Boolean, "version": Boolean, - "define": [String, Array] + "define": [String, Array], + "no-telemetry": Boolean }; var shortHands = { "?":["--help"], @@ -97,6 +98,7 @@ if (parsedArgs.help) { console.log(" --safe enable safe mode"); console.log(" -D, --define X=Y overwrite value in settings file"); console.log(" --version show version information"); + console.log(" --no-telemetry do not share usage data with the Node-RED project"); console.log(" -?, --help show this help"); console.log(" admin run an admin command"); console.log(""); @@ -222,6 +224,10 @@ if (process.env.NODE_RED_ENABLE_TOURS) { settings.editorTheme.tours = !/^false$/i.test(process.env.NODE_RED_ENABLE_TOURS); } +if (parsedArgs.telemetry === false || process.env.NODE_RED_DISABLE_TELEMETRY) { + settings.telemetry = settings.telemetry || {}; + settings.telemetry.enabled = false; +} var defaultServerSettings = { "x-powered-by": false From bd244027c62e9fecdad81f8c48a42d2fb323cf85 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:33:38 +0100 Subject: [PATCH 03/16] Move 4.0 tour to archive and reset for 4.1 --- .../editor-client/src/js/ui/tour/tourGuide.js | 7 +- .../{ => 4.0}/images/nr4-auto-complete.png | Bin .../images/nr4-background-deploy.png | Bin .../{ => 4.0}/images/nr4-config-select.png | Bin .../{ => 4.0}/images/nr4-diff-update.png | Bin .../images/nr4-multiplayer-location.png | Bin .../{ => 4.0}/images/nr4-multiplayer.png | Bin .../tours/{ => 4.0}/images/nr4-plugins.png | Bin .../tours/{ => 4.0}/images/nr4-sf-config.png | Bin .../images/nr4-timestamp-formatting.png | Bin .../editor-client/src/tours/4.0/welcome.js | 231 ++++++++++++++++ .../editor-client/src/tours/welcome.js | 251 +++--------------- 12 files changed, 277 insertions(+), 212 deletions(-) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-auto-complete.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-background-deploy.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-config-select.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-diff-update.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-multiplayer-location.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-multiplayer.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-plugins.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-sf-config.png (100%) rename packages/node_modules/@node-red/editor-client/src/tours/{ => 4.0}/images/nr4-timestamp-formatting.png (100%) create mode 100644 packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js index 5ae66720c..30130d2d1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tour/tourGuide.js @@ -435,10 +435,15 @@ RED.tourGuide = (function() { function listTour() { return [ + { + id: "4_1", + label: "4.1", + path: "./tours/welcome.js" + }, { id: "4_0", label: "4.0", - path: "./tours/welcome.js" + path: "./tours/4.0/welcome.js" }, { id: "3_1", diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-auto-complete.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-auto-complete.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-auto-complete.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-auto-complete.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-background-deploy.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-background-deploy.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-background-deploy.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-config-select.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-config-select.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-config-select.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-config-select.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-diff-update.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-diff-update.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-diff-update.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-multiplayer-location.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer-location.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-multiplayer-location.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-multiplayer.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-multiplayer.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-multiplayer.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-plugins.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-plugins.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-plugins.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-plugins.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-sf-config.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-sf-config.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-sf-config.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-sf-config.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/images/nr4-timestamp-formatting.png b/packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-timestamp-formatting.png similarity index 100% rename from packages/node_modules/@node-red/editor-client/src/tours/images/nr4-timestamp-formatting.png rename to packages/node_modules/@node-red/editor-client/src/tours/4.0/images/nr4-timestamp-formatting.png diff --git a/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js new file mode 100644 index 000000000..02a559136 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/tours/4.0/welcome.js @@ -0,0 +1,231 @@ +export default { + version: "4.0.0", + steps: [ + { + titleIcon: "fa fa-map-o", + title: { + "en-US": "Welcome to Node-RED 4.0!", + "ja": "Node-RED 4.0 へようこそ!", + "fr": "Bienvenue dans Node-RED 4.0!" + }, + description: { + "en-US": "

Let's take a moment to discover the new features in this release.

", + "ja": "

本リリースの新機能を見つけてみましょう。

", + "fr": "

Prenons un moment pour découvrir les nouvelles fonctionnalités de cette version.

" + } + }, + { + title: { + "en-US": "Multiplayer Mode", + "ja": "複数ユーザ同時利用モード", + "fr": "Mode Multi-utilisateur" + }, + image: 'images/nr4-multiplayer-location.png', + description: { + "en-US": `

This release includes the first small steps towards making Node-RED easier + to work with when you have multiple people editing flows at the same time.

+

When this feature is enabled, you will now see who else has the editor open and some + basic information on where they are in the editor.

+

Check the release post for details on how to enable this feature in your settings file.

`, + "ja": `

本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。

+

本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。

+

設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。

`, + "fr": `

Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser + lorsque plusieurs personnes modifient des flux en même temps.

+

Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si d’autres utilisateurs ont + ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.

+

Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité + dans votre fichier de paramètres.

` + } + }, + { + title: { + "en-US": "Better background deploy handling", + "ja": "バックグラウンドのデプロイ処理の改善", + "fr": "Meilleure gestion du déploiement en arrière-plan" + }, + image: 'images/nr4-background-deploy.png', + description: { + "en-US": `

If another user deploys changes whilst you are editing, we now use a more discrete notification + that doesn't stop you continuing your work - especially if they are being very productive and deploying lots + of changes.

`, + "ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`, + "fr": `

Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez + une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.

` + } + }, + { + title: { + "en-US": "Improved flow diffs", + "ja": "フローの差分表示の改善", + "fr": "Amélioration des différences de flux" + }, + image: 'images/nr4-diff-update.png', + description: { + "en-US": `

When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration + changes and those that have only been moved.

+

When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.

`, + "ja": `

フローの変更内容を表示する時に、Node-REDは設定が変更されたノードと、移動されただけのノードを区別するようになりました。

+

これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。

`, + "fr": `

Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les + noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.

+

Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les + plus importants.

` + } + }, + { + title: { + "en-US": "Better Configuration Node UX", + "ja": "設定ノードのUXが向上", + "fr": "Meilleure expérience utilisateur du noeud de configuration" + }, + image: 'images/nr4-config-select.png', + description: { + "en-US": `

The Configuration node selection UI has had a small update to have a dedicated 'add' button + next to the select box.

+

It's a small change, but should make it easier to work with your config nodes.

`, + "ja": `

設定ノードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。

+

微修正ですが設定ノードの操作が容易になります。

`, + "fr": `

L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite + mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.

+

C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.

` + } + }, + { + title: { + "en-US": "Timestamp formatting options", + "ja": "タイムスタンプの形式の項目", + "fr": "Options de formatage de l'horodatage" + }, + image: 'images/nr4-timestamp-formatting.png', + description: { + "en-US": `

Nodes that let you set a timestamp now have options on what format that timestamp should be in.

+

We're keeping it simple to begin with by providing three options:

+

    +
  • Milliseconds since epoch - this is existing behaviour of the timestamp option
  • +
  • ISO 8601 - a common format used by many systems
  • +
  • JavaScript Date Object
  • +
`, + "ja": `

タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。

+

次の3つの項目を追加したことで、簡単に選択できるようになりました:

+

    +
  • エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目
  • +
  • ISO 8601 - 多くのシステムで使用されている共通の形式
  • +
  • JavaScript日付オブジェクト
  • +
`, + "fr": `

Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.

+

Nous gardons les choses simples en proposant trois options :

+

    +
  • Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage
  • +
  • ISO 8601 : un format commun utilisé par de nombreux systèmes
  • +
  • Objet Date JavaScript
  • +
` + } + }, + { + title: { + "en-US": "Auto-complete of flow/global and env types", + "ja": "フロー/グローバル、環境変数の型の自動補完", + "fr": "Saisie automatique des types de flux/global et env" + }, + image: 'images/nr4-auto-complete.png', + description: { + "en-US": `

The flow/global context inputs and the env input + now all include auto-complete suggestions based on the live state of your flows.

+ `, + "ja": `

flow/globalコンテキストやenvの入力を、現在のフローの状態をもとに自動補完で提案するようになりました。

+ `, + "fr": `

Les entrées contextuelles flow/global et l'entrée env + incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.

+ `, + } + }, + { + title: { + "en-US": "Config node customisation in Subflows", + "ja": "サブフローでの設定ノードのカスタマイズ", + "fr": "Personnalisation du noeud de configuration dans les sous-flux" + }, + image: 'images/nr4-sf-config.png', + description: { + "en-US": `

Subflows can now be customised to allow each instance to use a different + config node of a selected type.

+

For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing + of the messages received can be pointed at a different broker.

+ `, + "ja": `

サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。

+

例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。

+ `, + "fr": `

Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un + noeud de configuration d'un type sélectionné.

+

Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement + des messages reçus peut être pointée vers un autre courtier.

+ ` + } + }, + { + title: { + "en-US": "Remembering palette state", + "ja": "パレットの状態を維持", + "fr": "Mémorisation de l'état de la palette" + }, + description: { + "en-US": `

The palette now remembers what categories you have hidden between reloads - as well as any + filter you have applied.

`, + "ja": `

パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。

`, + "fr": `

La palette se souvient désormais des catégories que vous avez masquées entre les rechargements, + ainsi que le filtre que vous avez appliqué.

` + } + }, + { + title: { + "en-US": "Plugins shown in the Palette Manager", + "ja": "パレット管理にプラグインを表示", + "fr": "Affichage des Plugins dans le gestionnaire de palettes" + }, + image: 'images/nr4-plugins.png', + description: { + "en-US": `

The palette manager now shows any plugin modules you have installed, such as + node-red-debugger. Previously they would only be shown if the plugins include + nodes for the palette.

`, + "ja": `

パレットの管理に node-red-debugger の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。

`, + "fr": `

Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés, + tels que node-red-debugger. Auparavant, ils n'étaient affichés que s'ils contenaient + des noeuds pour la palette.

` + } + }, + { + title: { + "en-US": "Node Updates", + "ja": "ノードの更新", + "fr": "Mises à jour des noeuds" + }, + // image: "images/", + description: { + "en-US": `

The core nodes have received lots of minor fixes, documentation updates and + small enhancements. Check the full changelog in the Help sidebar for a full list.

+
    +
  • A fully RFC4180 compliant CSV mode
  • +
  • Customisable headers on the WebSocket node
  • +
  • Split node now can operate on any message property
  • +
  • and lots more...
  • +
`, + "ja": `

コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。

+
    +
  • RFC4180に完全に準拠したCSVモード
  • +
  • WebSocketノードのカスタマイズ可能なヘッダ
  • +
  • Splitノードは、メッセージプロパティで操作できるようになりました
  • +
  • 他にも沢山あります...
  • +
`, + "fr": `

Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour. + Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :

+
    +
  • Un mode CSV entièrement conforme à la norme RFC4180
  • +
  • En-têtes personnalisables pour le noeud WebSocket
  • +
  • Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message
  • +
  • Et bien plus encore...
  • +
` + } + } + ] +} diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index 02a559136..ab09e8455 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -1,12 +1,12 @@ export default { - version: "4.0.0", + version: "4.1.0", steps: [ { titleIcon: "fa fa-map-o", title: { - "en-US": "Welcome to Node-RED 4.0!", - "ja": "Node-RED 4.0 へようこそ!", - "fr": "Bienvenue dans Node-RED 4.0!" + "en-US": "Welcome to Node-RED 4.1!", + "ja": "Node-RED 4.1 へようこそ!", + "fr": "Bienvenue dans Node-RED 4.1!" }, description: { "en-US": "

Let's take a moment to discover the new features in this release.

", @@ -16,216 +16,45 @@ export default { }, { title: { - "en-US": "Multiplayer Mode", - "ja": "複数ユーザ同時利用モード", - "fr": "Mode Multi-utilisateur" + "en-US": "Something new", }, - image: 'images/nr4-multiplayer-location.png', + // image: 'images/nr4-multiplayer-location.png', description: { - "en-US": `

This release includes the first small steps towards making Node-RED easier - to work with when you have multiple people editing flows at the same time.

-

When this feature is enabled, you will now see who else has the editor open and some - basic information on where they are in the editor.

-

Check the release post for details on how to enable this feature in your settings file.

`, - "ja": `

本リリースには、複数ユーザが同時にフローを編集する時に、Node-REDをより使いやすくするのための最初の微修正が入っています。

-

本機能を有効にすると、誰がエディタを開いているか、その人がエディタ上のどこにいるかの基本的な情報が表示されます。

-

設定ファイルで本機能を有効化する方法の詳細は、リリースの投稿を確認してください。

`, - "fr": `

Cette version inclut les premières étapes visant à rendre Node-RED plus facile à utiliser - lorsque plusieurs personnes modifient des flux en même temps.

-

Lorsque cette fonctionnalité est activée, vous pourrez désormais voir si d’autres utilisateurs ont - ouvert l'éditeur. Vous pourrez également savoir où ces utilisateurs se trouvent dans l'éditeur.

-

Consultez la note de publication pour plus de détails sur la façon d'activer cette fonctionnalité - dans votre fichier de paramètres.

` + "en-US": `

Something new

` } }, - { - title: { - "en-US": "Better background deploy handling", - "ja": "バックグラウンドのデプロイ処理の改善", - "fr": "Meilleure gestion du déploiement en arrière-plan" - }, - image: 'images/nr4-background-deploy.png', - description: { - "en-US": `

If another user deploys changes whilst you are editing, we now use a more discrete notification - that doesn't stop you continuing your work - especially if they are being very productive and deploying lots - of changes.

`, - "ja": `他のユーザが変更をデプロイした時に、特に変更が多い生産的な編集作業を妨げないように通知するようになりました。`, - "fr": `

Si un autre utilisateur déploie des modifications pendant que vous êtes en train de modifier, vous recevrez - une notification plus discrète qu'auparavant qui ne vous empêche pas de continuer votre travail.

` - } - }, - { - title: { - "en-US": "Improved flow diffs", - "ja": "フローの差分表示の改善", - "fr": "Amélioration des différences de flux" - }, - image: 'images/nr4-diff-update.png', - description: { - "en-US": `

When viewing changes made to a flow, Node-RED now distinguishes between nodes that have had configuration - changes and those that have only been moved.

-

When faced with a long list of changes to look at, this makes it much easier to focus on more significant items.

`, - "ja": `

フローの変更内容を表示する時に、Node-REDは設定が変更されたノードと、移動されただけのノードを区別するようになりました。

-

これによって、多くの変更内容を確認する際に、重要な項目に焦点を当てることができます。

`, - "fr": `

Lors de l'affichage des modifications apportées à un flux, Node-RED fait désormais la distinction entre les - noeuds qui ont changé de configuration et ceux qui ont seulement été déplacés.

-

Face à une longue liste de changements à examiner, il est beaucoup plus facile de se concentrer sur les éléments les - plus importants.

` - } - }, - { - title: { - "en-US": "Better Configuration Node UX", - "ja": "設定ノードのUXが向上", - "fr": "Meilleure expérience utilisateur du noeud de configuration" - }, - image: 'images/nr4-config-select.png', - description: { - "en-US": `

The Configuration node selection UI has had a small update to have a dedicated 'add' button - next to the select box.

-

It's a small change, but should make it easier to work with your config nodes.

`, - "ja": `

設定ノードを選択するUIが修正され、選択ボックスの隣に専用の「追加」ボタンが追加されました。

-

微修正ですが設定ノードの操作が容易になります。

`, - "fr": `

L'interface utilisateur de la sélection du noeud de configuration a fait l'objet d'une petite - mise à jour afin de disposer d'un bouton « Ajouter » à côté de la zone de sélection.

-

C'est un petit changement, mais cela devrait faciliter le travail avec vos noeuds de configuration.

` - } - }, - { - title: { - "en-US": "Timestamp formatting options", - "ja": "タイムスタンプの形式の項目", - "fr": "Options de formatage de l'horodatage" - }, - image: 'images/nr4-timestamp-formatting.png', - description: { - "en-US": `

Nodes that let you set a timestamp now have options on what format that timestamp should be in.

-

We're keeping it simple to begin with by providing three options:

-

    -
  • Milliseconds since epoch - this is existing behaviour of the timestamp option
  • -
  • ISO 8601 - a common format used by many systems
  • -
  • JavaScript Date Object
  • -
`, - "ja": `

タイムスタンプを設定するノードに、タイムスタンプの形式を指定できる項目が追加されました。

-

次の3つの項目を追加したことで、簡単に選択できるようになりました:

-

    -
  • エポックからのミリ秒 - 従来動作と同じになるタイムスタンプの項目
  • -
  • ISO 8601 - 多くのシステムで使用されている共通の形式
  • -
  • JavaScript日付オブジェクト
  • -
`, - "fr": `

Les noeuds qui vous permettent de définir un horodatage disposent désormais d'options sur le format dans lequel cet horodatage peut être défini.

-

Nous gardons les choses simples en proposant trois options :

-

    -
  • Millisecondes depuis l'époque : il s'agit du comportement existant de l'option d'horodatage
  • -
  • ISO 8601 : un format commun utilisé par de nombreux systèmes
  • -
  • Objet Date JavaScript
  • -
` - } - }, - { - title: { - "en-US": "Auto-complete of flow/global and env types", - "ja": "フロー/グローバル、環境変数の型の自動補完", - "fr": "Saisie automatique des types de flux/global et env" - }, - image: 'images/nr4-auto-complete.png', - description: { - "en-US": `

The flow/global context inputs and the env input - now all include auto-complete suggestions based on the live state of your flows.

- `, - "ja": `

flow/globalコンテキストやenvの入力を、現在のフローの状態をもとに自動補完で提案するようになりました。

- `, - "fr": `

Les entrées contextuelles flow/global et l'entrée env - incluent désormais des suggestions de saisie semi-automatique basées sur l'état actuel de vos flux.

- `, - } - }, - { - title: { - "en-US": "Config node customisation in Subflows", - "ja": "サブフローでの設定ノードのカスタマイズ", - "fr": "Personnalisation du noeud de configuration dans les sous-flux" - }, - image: 'images/nr4-sf-config.png', - description: { - "en-US": `

Subflows can now be customised to allow each instance to use a different - config node of a selected type.

-

For example, each instance of a subflow that connects to an MQTT Broker and does some post-processing - of the messages received can be pointed at a different broker.

- `, - "ja": `

サブフローをカスタマイズして、選択した型の異なる設定ノードを各インスタンスが使用できるようになりました。

-

例えば、MQTTブローカへ接続し、メッセージ受信と後処理を行うサブフローの各インスタンスに異なるブローカを指定することも可能です。

- `, - "fr": `

Les sous-flux peuvent désormais être personnalisés pour permettre à chaque instance d'utiliser un - noeud de configuration d'un type sélectionné.

-

Par exemple, chaque instance d'un sous-flux qui se connecte à un courtier MQTT et effectue un post-traitement - des messages reçus peut être pointée vers un autre courtier.

- ` - } - }, - { - title: { - "en-US": "Remembering palette state", - "ja": "パレットの状態を維持", - "fr": "Mémorisation de l'état de la palette" - }, - description: { - "en-US": `

The palette now remembers what categories you have hidden between reloads - as well as any - filter you have applied.

`, - "ja": `

パレット上で非表示にしたカテゴリや適用したフィルタが、リロードしても記憶されるようになりました。

`, - "fr": `

La palette se souvient désormais des catégories que vous avez masquées entre les rechargements, - ainsi que le filtre que vous avez appliqué.

` - } - }, - { - title: { - "en-US": "Plugins shown in the Palette Manager", - "ja": "パレット管理にプラグインを表示", - "fr": "Affichage des Plugins dans le gestionnaire de palettes" - }, - image: 'images/nr4-plugins.png', - description: { - "en-US": `

The palette manager now shows any plugin modules you have installed, such as - node-red-debugger. Previously they would only be shown if the plugins include - nodes for the palette.

`, - "ja": `

パレットの管理に node-red-debugger の様なインストールしたプラグインが表示されます。以前はプラグインにパレット向けのノードが含まれている時のみ表示されていました。

`, - "fr": `

Le gestionnaire de palettes affiche désormais tous les plugins que vous avez installés, - tels que node-red-debugger. Auparavant, ils n'étaient affichés que s'ils contenaient - des noeuds pour la palette.

` - } - }, - { - title: { - "en-US": "Node Updates", - "ja": "ノードの更新", - "fr": "Mises à jour des noeuds" - }, - // image: "images/", - description: { - "en-US": `

The core nodes have received lots of minor fixes, documentation updates and - small enhancements. Check the full changelog in the Help sidebar for a full list.

-
    -
  • A fully RFC4180 compliant CSV mode
  • -
  • Customisable headers on the WebSocket node
  • -
  • Split node now can operate on any message property
  • -
  • and lots more...
  • -
`, - "ja": `

コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。

-
    -
  • RFC4180に完全に準拠したCSVモード
  • -
  • WebSocketノードのカスタマイズ可能なヘッダ
  • -
  • Splitノードは、メッセージプロパティで操作できるようになりました
  • -
  • 他にも沢山あります...
  • -
`, - "fr": `

Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour. - Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :

-
    -
  • Un mode CSV entièrement conforme à la norme RFC4180
  • -
  • En-têtes personnalisables pour le noeud WebSocket
  • -
  • Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message
  • -
  • Et bien plus encore...
  • -
` - } - } + // { + // title: { + // "en-US": "Node Updates", + // "ja": "ノードの更新", + // "fr": "Mises à jour des noeuds" + // }, + // // image: "images/", + // description: { + // "en-US": `

The core nodes have received lots of minor fixes, documentation updates and + // small enhancements. Check the full changelog in the Help sidebar for a full list.

+ //
    + //
  • A fully RFC4180 compliant CSV mode
  • + //
  • Customisable headers on the WebSocket node
  • + //
  • Split node now can operate on any message property
  • + //
  • and lots more...
  • + //
`, + // "ja": `

コアノードには沢山の軽微な修正、ドキュメント更新、小さな機能拡張が入っています。全リストはヘルプサイドバーにある変更履歴を参照してください。

+ //
    + //
  • RFC4180に完全に準拠したCSVモード
  • + //
  • WebSocketノードのカスタマイズ可能なヘッダ
  • + //
  • Splitノードは、メッセージプロパティで操作できるようになりました
  • + //
  • 他にも沢山あります...
  • + //
`, + // "fr": `

Les noeuds principaux ont reçu de nombreux correctifs mineurs ainsi que des améliorations. La documentation a été mise à jour. + // Consultez le journal des modifications dans la barre latérale d'aide pour une liste complète. Ci-dessous, les changements les plus importants :

+ //
    + //
  • Un mode CSV entièrement conforme à la norme RFC4180
  • + //
  • En-têtes personnalisables pour le noeud WebSocket
  • + //
  • Le noeud Split peut désormais fonctionner sur n'importe quelle propriété de message
  • + //
  • Et bien plus encore...
  • + //
` + // } + // } ] } From 36f533f3905459db68a454729c2c88cb7718f5db Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:34:52 +0100 Subject: [PATCH 04/16] Add telemetry consent dialog --- .../editor-client/locales/en-US/editor.json | 10 +++++ .../@node-red/editor-client/src/js/red.js | 41 ++++++++++++++++--- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 5a35135ee..196d032ca 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1275,5 +1275,15 @@ "environment": "Environment", "header": "Global Environment Variables", "revert": "Revert" + }, + "telemetry": { + "label": "Telemetry", + "settingsTitle": "Share Anonymous Usage Information", + "settingsDescription": "

Node-RED would like to send anonymous usage data back to the Node-RED team. This information helps us to understand how it is used.

For full information on what information is collected and how it is used, please see the telemetry documentation.

", + "settingsDescription2": "

You can change this setting at any time in the editor settings.

", + "enableLabel": "Yes, send my usage data", + "disableLabel": "No, do not send my usage data", + "updateAvailable": "Update available", + "updateAvailableDesc": "Node-RED __version__ is now available" } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index f8130a686..d723101e4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -672,14 +672,43 @@ var RED = (function() { setTimeout(function() { loader.end(); - checkFirstRun(function() { - if (showProjectWelcome) { - RED.projects.showStartup(); - } - }); + checkTelemetry(function () { + checkFirstRun(function() { + if (showProjectWelcome) { + RED.projects.showStartup(); + } + }); + }) },100); } - + function checkTelemetry(done) { + const telemetrySettings = RED.settings.telemetryEnabled; + // Can only get telemetry permission from a user with permission to modify settings + if (RED.user.hasPermission("settings.write") && telemetrySettings === undefined) { + function completeTelemetry(enable) { + RED.settings.set("telemetryEnabled", enable) + dialog.close() + done() + } + const dialog = RED.popover.dialog({ + title: RED._("telemetry.settingsTitle"), + content: `${RED._("telemetry.settingsDescription")}${RED._("telemetry.settingsDescription2")}`, + closeButton: false, + buttons: [ + { + text: RED._("telemetry.enableLabel"), + click: () => completeTelemetry(true) + }, + { + text: RED._("telemetry.disableLabel"), + click: () => completeTelemetry(false) + } + ] + }) + } else { + done() + } + } function checkFirstRun(done) { if (RED.settings.theme("tours") === false) { done(); From 86558126ab77bcbcabcb5d75703ba785b0482776 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:35:24 +0100 Subject: [PATCH 05/16] Add telemetry settings to user settings dialog --- .../editor-client/src/js/ui/userSettings.js | 25 +++++++++++++++++-- .../editor-client/src/sass/userSettings.scss | 6 +++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js index 1b61f396e..52caa3d9b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js @@ -143,6 +143,18 @@ RED.userSettings = (function() { {setting:"view-node-show-label",label:"menu.label.showNodeLabelDefault",default: true, toggle:true} ] }, + { + title: "telemetry.label", + options: [ + { + global: true, + setting: "telemetryEnabled", + label: "telemetry.settingsTitle", + description: "telemetry.settingsDescription", + toggle: true + }, + ] + }, { title: "menu.label.other", options: [ @@ -169,13 +181,20 @@ RED.userSettings = (function() { var initialState; if (opt.local) { initialState = localStorage.getItem(opt.setting); + } else if (opt.global) { + initialState = RED.settings.get(opt.setting); } else { initialState = currentEditorSettings.view[opt.setting]; } var row = $('
').appendTo(pane); var input; if (opt.toggle) { - input = $('').appendTo(row).find("input"); + let label = RED._(opt.label) + if (opt.description) { + label = `

${label}

${RED._(opt.description)}`; + } + input = $('').appendTo(row) + $('').appendTo(row) input.prop('checked',initialState); } else if (opt.options) { $('').appendTo(row); @@ -209,6 +228,8 @@ RED.userSettings = (function() { var opt = allSettings[id]; if (opt.local) { localStorage.setItem(opt.setting,value); + } else if (opt.global) { + RED.settings.set(opt.setting, value) } else { var currentEditorSettings = RED.settings.get('editor') || {}; currentEditorSettings.view = currentEditorSettings.view || {}; @@ -237,7 +258,7 @@ RED.userSettings = (function() { addPane({ id:'view', - title: RED._("menu.label.view.view"), + title: RED._("menu.label.settings"), get: createViewPane, close: function() { viewSettings.forEach(function(section) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss index 5e0c7fa47..7abae094c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/userSettings.scss @@ -70,8 +70,14 @@ overflow-y: auto; } .red-ui-settings-row { + display: flex; + gap: 10px; + align-items:flex-start; padding: 5px 10px 2px; } +.red-ui-settings-row input[type="checkbox"] { + margin-top: 8px; +} .red-ui-settings-section { position: relative; &:after { From 6220f990c4fc1c6d9735531fc54759109ab84afe Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:35:58 +0100 Subject: [PATCH 06/16] Add update available widget --- .../@node-red/editor-client/src/js/red.js | 19 ++++++++++++++++++- .../editor-client/src/js/ui/palette-editor.js | 6 +++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index d723101e4..c2afd74cd 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -317,6 +317,7 @@ var RED = (function() { function completeLoad(showProjectWelcome) { var persistentNotifications = {}; + let updateAvailableWidget = null RED.comms.subscribe("notification/#",function(topic,msg) { var parts = topic.split("/"); var notificationId = parts[1]; @@ -358,7 +359,23 @@ var RED = (function() { }); return; } - + if (notificationId === "update-available") { + if (!updateAvailableWidget) { + updateAvailableWidget = $('').text(RED._("telemetry.updateAvailable")); + updateAvailableWidget.on("click", function (evt) { + window.open(`https://github.com/node-red/node-red/releases/tag/${msg.version}`, "_blank"); + }); + const tooltip = RED.popover.tooltip(updateAvailableWidget, function () { + return RED._("telemetry.updateAvailableDesc", msg) + }); + RED.statusBar.add({ + id: "red-ui-status-update-available", + align: "right", + element: updateAvailableWidget + }); + setTimeout(() => { tooltip.open(); setTimeout(() => tooltip.close(), 7000) }, 1000) + } + } if (msg.text) { msg.default = msg.text; var text = RED._(msg.text,msg); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 3926ca430..9ed9e9bb7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -1525,7 +1525,7 @@ RED.palette.editor = (function() { }); RED.statusBar.add({ - id: "update", + id: "red-ui-status-package-update", align: "right", element: updateStatusWidget }); @@ -1560,11 +1560,11 @@ RED.palette.editor = (function() { function updateStatus(opts) { if (opts.count) { - RED.statusBar.show("update"); + RED.statusBar.show("red-ui-status-package-update"); updateStatusWidget.empty(); $(' ' + opts.count + '').appendTo(updateStatusWidget); } else { - RED.statusBar.hide("update"); + RED.statusBar.hide("red-ui-status-package-update"); } } From 12cfc9175a0e6633b7d1f1ddec8c2148fdf66c60 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:45:55 +0100 Subject: [PATCH 07/16] Add placeholder settings.js values for telemetry --- packages/node_modules/node-red/settings.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index e8bb01228..5c4ce0e94 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -273,6 +273,7 @@ module.exports = { * Runtime Settings * - lang * - runtimeState + * - telemetry * - diagnostics * - logging * - contextStorage @@ -311,6 +312,22 @@ module.exports = { /** show or hide runtime stop/start options in the node-red editor. Must be set to `false` to hide */ ui: false, }, + telemetry: { + /** + * By default, telemetry is disabled until the user provides consent the first + * time they open the editor. + * + * The following property can be uncommented and set to true/false to enable/disable + * telemetry without seeking further consent in the editor. + * The user can override this setting via the user settings dialog within the editor + */ + // enabled: true, + /** + * If telemetry is enabled, the editor will notify the user if a new version of Node-RED + * is available. Set the following property to false to disable this notification. + */ + // updateNotification: true + }, /** Configure the logging output */ logging: { /** Only console logging is currently supported */ From 2f099a57f6c200f46f6c086b7116ce72b3a1cca7 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 23 Apr 2025 17:48:11 +0100 Subject: [PATCH 08/16] Set ping schedule to 5 mins after startup, then 24hrs --- .../node_modules/@node-red/runtime/lib/telemetry/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js index fef1cc1db..8fe7de9f5 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -4,7 +4,7 @@ const semver = require('semver') const cronosjs = require('cronosjs') const METRICS_DIR = path.join(__dirname, 'metrics') -const INITIAL_PING_DELAY = 1000 * 60 * 30 // 30 minutes from startup +const INITIAL_PING_DELAY = 1000 * 60 * 5 // 5 minutes from startup let runtime @@ -136,8 +136,8 @@ function startTelemetry () { } const pingTime = new Date(Date.now() + INITIAL_PING_DELAY) - const pingMinutes = '*'//pingTime.getMinutes() - const pingHours = '*'//pingTime.getHours() + const pingMinutes = pingTime.getMinutes() + const pingHours = pingTime.getHours() const pingSchedule = `${pingMinutes} ${pingHours} * * *` runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`) From 350ab52b9981f3eacfbd62adcbcd5c1e8b1cdd31 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 11:39:56 +0100 Subject: [PATCH 09/16] Update consent message --- .../@node-red/editor-client/locales/en-US/editor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 196d032ca..892ba34ac 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1279,7 +1279,7 @@ "telemetry": { "label": "Telemetry", "settingsTitle": "Share Anonymous Usage Information", - "settingsDescription": "

Node-RED would like to send anonymous usage data back to the Node-RED team. This information helps us to understand how it is used.

For full information on what information is collected and how it is used, please see the telemetry documentation.

", + "settingsDescription": "

Node-RED would like to send anonymous usage data back to the Node-RED team. This information helps us to understand how it is used.

It does not include any details of your flows or users.

For full information on what information is collected and how it is used, please see the telemetry documentation.

", "settingsDescription2": "

You can change this setting at any time in the editor settings.

", "enableLabel": "Yes, send my usage data", "disableLabel": "No, do not send my usage data", From 0d4854a079433e2558fcd7849be001073d8abdaf Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 13:35:06 +0100 Subject: [PATCH 10/16] Fix linting --- .../@node-red/editor-client/src/js/red.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index c2afd74cd..ab7effd89 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -698,15 +698,12 @@ var RED = (function() { }) },100); } + function checkTelemetry(done) { const telemetrySettings = RED.settings.telemetryEnabled; // Can only get telemetry permission from a user with permission to modify settings if (RED.user.hasPermission("settings.write") && telemetrySettings === undefined) { - function completeTelemetry(enable) { - RED.settings.set("telemetryEnabled", enable) - dialog.close() - done() - } + const dialog = RED.popover.dialog({ title: RED._("telemetry.settingsTitle"), content: `${RED._("telemetry.settingsDescription")}${RED._("telemetry.settingsDescription2")}`, @@ -714,11 +711,19 @@ var RED = (function() { buttons: [ { text: RED._("telemetry.enableLabel"), - click: () => completeTelemetry(true) + click: () => { + RED.settings.set("telemetryEnabled", true) + dialog.close() + done() + } }, { text: RED._("telemetry.disableLabel"), - click: () => completeTelemetry(false) + click: () => { + RED.settings.set("telemetryEnabled", false) + dialog.close() + done() + } } ] }) From 7d4d604aa26bcea01cdbbbf2df3add2a5064f74a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 13:41:10 +0100 Subject: [PATCH 11/16] Increase initial delay to 30 minutes --- packages/node_modules/@node-red/runtime/lib/telemetry/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js index 8fe7de9f5..f389407a8 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -4,7 +4,7 @@ const semver = require('semver') const cronosjs = require('cronosjs') const METRICS_DIR = path.join(__dirname, 'metrics') -const INITIAL_PING_DELAY = 1000 * 60 * 5 // 5 minutes from startup +const INITIAL_PING_DELAY = 1000 * 60 * 30 // 5 minutes from startup let runtime From 0f3d25252b2c22353af37c9e51248b8abebfdad8 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 15:26:29 +0100 Subject: [PATCH 12/16] Add unit tests --- .../@node-red/runtime/lib/telemetry/index.js | 20 ++-- .../runtime/lib/telemetry/metrics/03-env.js | 2 +- .../runtime/lib/telemetry/index_spec.js | 96 +++++++++++++++++++ .../lib/telemetry/metrics/01-core_spec.js | 16 ++++ .../lib/telemetry/metrics/02-os_spec.js | 14 +++ .../lib/telemetry/metrics/03-env_spec.js | 17 ++++ 6 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 test/unit/@node-red/runtime/lib/telemetry/index_spec.js create mode 100644 test/unit/@node-red/runtime/lib/telemetry/metrics/01-core_spec.js create mode 100644 test/unit/@node-red/runtime/lib/telemetry/metrics/02-os_spec.js create mode 100644 test/unit/@node-red/runtime/lib/telemetry/metrics/03-env_spec.js diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js index f389407a8..2836d407b 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -106,16 +106,16 @@ function isTelemetryEnabled () { return undefined } - // If there are telemetry settings, use what it says - if (telemetrySettings && telemetrySettings.enabled !== undefined) { - return telemetrySettings.enabled - } - // User has made a choice; defer to that if (runtimeTelemetryEnabled !== undefined) { return runtimeTelemetryEnabled } + // If there are telemetry settings, use what it says + if (telemetrySettings && telemetrySettings.enabled !== undefined) { + return telemetrySettings.enabled + } + // At this point, we have no sign the user has consented to telemetry, so // keep disabled - but return undefined as a false-like value to distinguish // it from the explicit disable above @@ -157,6 +157,7 @@ function stopTelemetry () { module.exports = { init: (_runtime) => { runtime = _runtime + if (isTelemetryEnabled()) { startTelemetry() } @@ -185,5 +186,12 @@ module.exports = { * Get telemetry enabled status * @returns {boolean} true if telemetry is enabled, false if disabled, undefined if not set */ - isEnabled: isTelemetryEnabled + isEnabled: isTelemetryEnabled, + + stop: () => { + if (scheduleTask) { + scheduleTask.stop() + scheduleTask = null + } + } } \ No newline at end of file diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js index 812c3ee4e..173adc752 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/metrics/03-env.js @@ -1,6 +1,6 @@ const process = require('process') -module.exports = async (runtime) => { +module.exports = (runtime) => { return { 'env.nodejs': process.version.replace(/^v/, ''), 'env.node-red': runtime.settings.version diff --git a/test/unit/@node-red/runtime/lib/telemetry/index_spec.js b/test/unit/@node-red/runtime/lib/telemetry/index_spec.js new file mode 100644 index 000000000..7ab1b89d1 --- /dev/null +++ b/test/unit/@node-red/runtime/lib/telemetry/index_spec.js @@ -0,0 +1,96 @@ +const should = require("should"); +const NR_TEST_UTILS = require("nr-test-utils"); + +const telemetryApi = NR_TEST_UTILS.require("@node-red/runtime/lib/telemetry/index"); + +describe("telemetry", function() { + + afterEach(function () { + telemetryApi.stop() + messages = [] + }) + + let messages = [] + + function getMockRuntime(settings) { + return { + settings: { + get: key => { return settings[key] }, + set: (key, value) => { settings[key] = value }, + available: () => true, + }, + log: { + debug: (msg) => { messages.push(msg)} + } + } + } + + // Principles to test: + // - No settings at all; disable telemetry + // - Runtime settings only; do what it says + // - User settings take precedence over runtime settings + + it('Disables telemetry with no settings present', function () { + telemetryApi.init(getMockRuntime({})) + messages.should.have.length(0) + // Returns undefined as we don't know either way + ;(telemetryApi.isEnabled() === undefined).should.be.true() + }) + it('Runtime settings - enable', function () { + // Enabled in runtime settings + telemetryApi.init(getMockRuntime({ + telemetry: { enabled: true } + })) + telemetryApi.isEnabled().should.be.true() + messages.should.have.length(1) + ;/Telemetry enabled/.test(messages[0]).should.be.true() + }) + it('Runtime settings - disable', function () { + telemetryApi.init(getMockRuntime({ + telemetry: { enabled: false }, + })) + // Returns false, not undefined + telemetryApi.isEnabled().should.be.false() + messages.should.have.length(0) + }) + + it('User settings - enable overrides runtime settings', function () { + telemetryApi.init(getMockRuntime({ + telemetry: { enabled: false }, + telemetryEnabled: true + })) + telemetryApi.isEnabled().should.be.true() + messages.should.have.length(1) + ;/Telemetry enabled/.test(messages[0]).should.be.true() + }) + + it('User settings - disable overrides runtime settings', function () { + telemetryApi.init(getMockRuntime({ + telemetry: { enabled: true }, + telemetryEnabled: false + })) + telemetryApi.isEnabled().should.be.false() + messages.should.have.length(0) + }) + + it('Can enable/disable telemetry', function () { + const settings = {} + telemetryApi.init(getMockRuntime(settings)) + ;(telemetryApi.isEnabled() === undefined).should.be.true() + + telemetryApi.enable() + + telemetryApi.isEnabled().should.be.true() + messages.should.have.length(1) + ;/Telemetry enabled/.test(messages[0]).should.be.true() + settings.should.have.property('telemetryEnabled', true) + + telemetryApi.disable() + + telemetryApi.isEnabled().should.be.false() + messages.should.have.length(2) + ;/Telemetry disabled/.test(messages[1]).should.be.true() + settings.should.have.property('telemetryEnabled', false) + + }) +}) \ No newline at end of file diff --git a/test/unit/@node-red/runtime/lib/telemetry/metrics/01-core_spec.js b/test/unit/@node-red/runtime/lib/telemetry/metrics/01-core_spec.js new file mode 100644 index 000000000..d1e012e5a --- /dev/null +++ b/test/unit/@node-red/runtime/lib/telemetry/metrics/01-core_spec.js @@ -0,0 +1,16 @@ +const should = require("should"); +const NR_TEST_UTILS = require("nr-test-utils"); + +const api = NR_TEST_UTILS.require("@node-red/runtime/lib/telemetry/metrics/01-core"); + +describe("telemetry metrics/01-core", function() { + + it('reports core metrics', function () { + const result = api({ + settings: { + get: key => { return {instanceId: "1234"}[key]} + } + }) + result.should.have.property("instanceId", "1234") + }) +}) \ No newline at end of file diff --git a/test/unit/@node-red/runtime/lib/telemetry/metrics/02-os_spec.js b/test/unit/@node-red/runtime/lib/telemetry/metrics/02-os_spec.js new file mode 100644 index 000000000..77a4b60af --- /dev/null +++ b/test/unit/@node-red/runtime/lib/telemetry/metrics/02-os_spec.js @@ -0,0 +1,14 @@ +const should = require("should"); +const NR_TEST_UTILS = require("nr-test-utils"); + +const api = NR_TEST_UTILS.require("@node-red/runtime/lib/telemetry/metrics/02-os"); + +describe("telemetry metrics/02-os", function() { + + it('reports os metrics', function () { + const result = api() + result.should.have.property("os.type") + result.should.have.property("os.release") + result.should.have.property("os.arch") + }) +}) \ No newline at end of file diff --git a/test/unit/@node-red/runtime/lib/telemetry/metrics/03-env_spec.js b/test/unit/@node-red/runtime/lib/telemetry/metrics/03-env_spec.js new file mode 100644 index 000000000..eff539270 --- /dev/null +++ b/test/unit/@node-red/runtime/lib/telemetry/metrics/03-env_spec.js @@ -0,0 +1,17 @@ +const should = require("should"); +const NR_TEST_UTILS = require("nr-test-utils"); + +const api = NR_TEST_UTILS.require("@node-red/runtime/lib/telemetry/metrics/03-env"); + +describe("telemetry metrics/03-env", function() { + + it('reports env metrics', function () { + const result = api({ + settings: { + version: '1.2.3' + } + }) + result.should.have.property("env.nodejs", process.version.replace(/^v/, '')) + result.should.have.property("env.node-red", '1.2.3') + }) +}) \ No newline at end of file From d7febe1bfbfdd50e12c6e6691236b8a3819649d5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 15:32:51 +0100 Subject: [PATCH 13/16] Fix runtime settings test --- .../@node-red/runtime/lib/api/settings_spec.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/unit/@node-red/runtime/lib/api/settings_spec.js b/test/unit/@node-red/runtime/lib/api/settings_spec.js index 9b3b94229..0e9e20422 100644 --- a/test/unit/@node-red/runtime/lib/api/settings_spec.js +++ b/test/unit/@node-red/runtime/lib/api/settings_spec.js @@ -57,7 +57,8 @@ describe("runtime-api/settings", function() { getCredentialKeyType: () => "test-key-type" }, library: {getLibraries: () => ["lib1"] }, - storage: {} + storage: {}, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({}).then(result => { result.should.have.property("httpNodeRoot","testHttpNodeRoot"); @@ -96,7 +97,8 @@ describe("runtime-api/settings", function() { getCredentialKeyType: () => "test-key-type" }, library: {getLibraries: () => { ["lib1"]} }, - storage: {} + storage: {}, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({ user: { @@ -145,7 +147,8 @@ describe("runtime-api/settings", function() { getCredentialsFilename: () => 'test-creds-file', getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} } - } + }, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({ user: { @@ -202,7 +205,8 @@ describe("runtime-api/settings", function() { getCredentialsFilename: () => 'test-creds-file', getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} } - } + }, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({ user: { @@ -250,7 +254,8 @@ describe("runtime-api/settings", function() { getCredentialsFilename: () => 'test-creds-file', getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} } - } + }, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({ user: { @@ -301,7 +306,8 @@ describe("runtime-api/settings", function() { getCredentialsFilename: () => 'test-creds-file', getGlobalGitUser: () => {return {name:'foo',email:'foo@example.com'}} } - } + }, + telemetry: { isEnabled: () => true } }) return settings.getRuntimeSettings({ user: { From 71f06941cd5fcc34929d0b36a6521ca33756c5b5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 24 Apr 2025 15:40:20 +0100 Subject: [PATCH 14/16] Handle unavailable settings --- .../@node-red/runtime/lib/telemetry/index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js index 2836d407b..3ac619639 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -98,8 +98,18 @@ function isTelemetryEnabled () { // If there are no telemetry settings then the user has not had a chance // to opt out yet - so keep it disabled until they do - const telemetrySettings = runtime.settings.get('telemetry') - const runtimeTelemetryEnabled = runtime.settings.get('telemetryEnabled') + let telemetrySettings + try { + telemetrySettings = runtime.settings.get('telemetry') + } catch (err) { + // Settings not available + } + let runtimeTelemetryEnabled + try { + runtimeTelemetryEnabled = runtime.settings.get('telemetryEnabled') + } catch (err) { + // Settings not available + } if (telemetrySettings === undefined && runtimeTelemetryEnabled === undefined) { // No telemetry settings - so keep it disabled @@ -138,7 +148,9 @@ function startTelemetry () { const pingTime = new Date(Date.now() + INITIAL_PING_DELAY) const pingMinutes = pingTime.getMinutes() const pingHours = pingTime.getHours() - const pingSchedule = `${pingMinutes} ${pingHours} * * *` + // const pingSchedule = `${pingMinutes} ${pingHours} * * *` + // DO NOT COMMIT! + const pingSchedule = `* * * * *` runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`) scheduleTask = cronosjs.scheduleTask(pingSchedule, () => { From 3a3571b37eec2314d3f471d68797b8846c3bcc21 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 25 Apr 2025 15:51:10 +0100 Subject: [PATCH 15/16] Consolidate update widgets --- .../@node-red/editor-client/src/js/red.js | 18 +----- .../editor-client/src/js/ui/common/popover.js | 6 ++ .../editor-client/src/js/ui/palette-editor.js | 61 +++++++++++++------ .../@node-red/runtime/lib/telemetry/index.js | 7 +-- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index ab7effd89..99cb8375b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -317,7 +317,6 @@ var RED = (function() { function completeLoad(showProjectWelcome) { var persistentNotifications = {}; - let updateAvailableWidget = null RED.comms.subscribe("notification/#",function(topic,msg) { var parts = topic.split("/"); var notificationId = parts[1]; @@ -360,21 +359,8 @@ var RED = (function() { return; } if (notificationId === "update-available") { - if (!updateAvailableWidget) { - updateAvailableWidget = $('').text(RED._("telemetry.updateAvailable")); - updateAvailableWidget.on("click", function (evt) { - window.open(`https://github.com/node-red/node-red/releases/tag/${msg.version}`, "_blank"); - }); - const tooltip = RED.popover.tooltip(updateAvailableWidget, function () { - return RED._("telemetry.updateAvailableDesc", msg) - }); - RED.statusBar.add({ - id: "red-ui-status-update-available", - align: "right", - element: updateAvailableWidget - }); - setTimeout(() => { tooltip.open(); setTimeout(() => tooltip.close(), 7000) }, 1000) - } + // re-emit as an event to be handled in editor-client/src/js/ui/palette-editor.js + RED.events.emit("notification/update-available", msg) } if (msg.text) { msg.default = msg.text; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js index 308f25f0e..a294bc484 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/popover.js @@ -163,13 +163,18 @@ RED.popover = (function() { } var timer = null; + let isOpen = false var active; var div; var contentDiv; var currentStyle; var openPopup = function(instant) { + if (isOpen) { + return + } if (active) { + isOpen = true var existingPopover = target.data("red-ui-popover"); if (options.tooltip && existingPopover) { active = false; @@ -334,6 +339,7 @@ RED.popover = (function() { } var closePopup = function(instant) { + isOpen = false $(document).off('mousedown.red-ui-popover'); if (!active) { if (div) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 9ed9e9bb7..7fc31e323 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -775,6 +775,14 @@ RED.palette.editor = (function() { } } }); + + RED.events.on("notification/update-available", function (msg) { + const updateKnownAbout = updateStatusState.version === msg.version + updateStatusState.version = msg.version + if (updateStatusWidgetPopover && !updateKnownAbout) { + setTimeout(() => { updateStatusWidgetPopover.open(); setTimeout(() => updateStatusWidgetPopover.close(), 20000) }, 1000) + } + }) } function getSettingsPane() { @@ -1509,19 +1517,34 @@ RED.palette.editor = (function() { } const updateStatusWidget = $(''); + let updateStatusWidgetPopover; + const updateStatusState = { moduleCount: 0 } let updateAvailable = []; function addUpdateInfoToStatusBar() { - updateStatusWidget.on("click", function (evt) { - RED.actions.invoke("core:manage-palette", { - view: "nodes", - filter: '"' + updateAvailable.join('", "') + '"' - }); - }); - - RED.popover.tooltip(updateStatusWidget, function () { - const count = updateAvailable.length || 0; - return RED._("palette.editor.updateCount", { count: count }); + updateStatusWidgetPopover = RED.popover.create({ + target: updateStatusWidget, + trigger: "click", + interactive: true, + direction: "bottom", + content: function () { + const count = updateAvailable.length || 0; + const content = $('
'); + if (updateStatusState.version) { + $(`${RED._("telemetry.updateAvailableDesc", updateStatusState)}`).appendTo(content) + } + if (count > 0) { + $(``).on("click", function (evt) { + updateStatusWidgetPopover.close() + RED.actions.invoke("core:manage-palette", { + view: "nodes", + filter: '"' + updateAvailable.join('", "') + '"' + }); + }).appendTo(content) + } + return content + }, + delay: { show: 750, hide: 250 } }); RED.statusBar.add({ @@ -1530,7 +1553,7 @@ RED.palette.editor = (function() { element: updateStatusWidget }); - updateStatus({ count: 0 }); + updateStatus(); } let pendingRefreshTimeout @@ -1553,16 +1576,20 @@ RED.palette.editor = (function() { } } } - - updateStatus({ count: updateAvailable.length }); + updateStatusState.moduleCount = updateAvailable.length; + updateStatus(); }, 200) } - function updateStatus(opts) { - if (opts.count) { - RED.statusBar.show("red-ui-status-package-update"); + function updateStatus() { + if (updateStatusState.moduleCount || updateStatusState.version) { updateStatusWidget.empty(); - $(' ' + opts.count + '').appendTo(updateStatusWidget); + let count = updateStatusState.moduleCount || 0; + if (updateStatusState.version) { + count ++ + } + $(` ${RED._("telemetry.updateAvailable", { count: count })}`).appendTo(updateStatusWidget); + RED.statusBar.show("red-ui-status-package-update"); } else { RED.statusBar.hide("red-ui-status-package-update"); } diff --git a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js index 3ac619639..3c9c5ce4d 100644 --- a/packages/node_modules/@node-red/runtime/lib/telemetry/index.js +++ b/packages/node_modules/@node-red/runtime/lib/telemetry/index.js @@ -4,7 +4,7 @@ const semver = require('semver') const cronosjs = require('cronosjs') const METRICS_DIR = path.join(__dirname, 'metrics') -const INITIAL_PING_DELAY = 1000 * 60 * 30 // 5 minutes from startup +const INITIAL_PING_DELAY = 1000 * 60 * 30 // 30 minutes from startup let runtime @@ -148,9 +148,8 @@ function startTelemetry () { const pingTime = new Date(Date.now() + INITIAL_PING_DELAY) const pingMinutes = pingTime.getMinutes() const pingHours = pingTime.getHours() - // const pingSchedule = `${pingMinutes} ${pingHours} * * *` - // DO NOT COMMIT! - const pingSchedule = `* * * * *` + const pingSchedule = `${pingMinutes} ${pingHours} * * *` + runtime.log.debug(`Telemetry enabled. Schedule: ${pingSchedule}`) scheduleTask = cronosjs.scheduleTask(pingSchedule, () => { From b540a0410507d9dee6cd6bea12af483cf8aefe46 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 9 May 2025 14:25:13 +0100 Subject: [PATCH 16/16] Update consent wording --- .../@node-red/editor-client/locales/en-US/editor.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 892ba34ac..b0c2c174b 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1277,12 +1277,12 @@ "revert": "Revert" }, "telemetry": { - "label": "Telemetry", - "settingsTitle": "Share Anonymous Usage Information", - "settingsDescription": "

Node-RED would like to send anonymous usage data back to the Node-RED team. This information helps us to understand how it is used.

It does not include any details of your flows or users.

For full information on what information is collected and how it is used, please see the telemetry documentation.

", + "label": "Update Notifications", + "settingsTitle": "Enable Update Notifications", + "settingsDescription": "

Node-RED can notify you when there is a new version available. This ensures you keep up to date with the latest features and fixes.

This requires sending anonymised data back to the Node-RED team. It does not include any details of your flows or users.

For full information on what information is collected and how it is used, please see the documentation.

", "settingsDescription2": "

You can change this setting at any time in the editor settings.

", - "enableLabel": "Yes, send my usage data", - "disableLabel": "No, do not send my usage data", + "enableLabel": "Yes, enable notifications", + "disableLabel": "No, do not enable notifications", "updateAvailable": "Update available", "updateAvailableDesc": "Node-RED __version__ is now available" }