| 3090 | } |
| 3091 | |
| 3092 | QDomElement MainWindow::saveDataProcessors(QDomDocument& doc) const { |
| 3093 | QDomElement element = doc.createElement(QStringLiteral("data_processors")); |
| 3094 | for (const auto& recipe : session_->sessionManager().dataProcessorService().recipes()) { |
| 3095 | // Resolve the input column to a stable (topic, field) path so it rebinds on |
| 3096 | // reload exactly like a plotted curve. |
| 3097 | const QString input_key = QStringLiteral("dataset:%1/topic:%2/column:%3") |
| 3098 | .arg(recipe.dataset_id) |
| 3099 | .arg(recipe.input_topic_id) |
| 3100 | .arg(recipe.input_column_index); |
| 3101 | const std::optional<CurveDescriptor> input_desc = session_->catalogModel().curveDescriptor(input_key); |
| 3102 | if (!input_desc.has_value()) { |
| 3103 | continue; // input field is gone; nothing to persist |
| 3104 | } |
| 3105 | QDomElement processor = doc.createElement(QStringLiteral("processor")); |
| 3106 | processor.setAttribute(QStringLiteral("input_topic"), input_desc->topic_name); |
| 3107 | processor.setAttribute(QStringLiteral("input_field"), input_desc->field_path); |
| 3108 | processor.setAttribute(QStringLiteral("processor_id"), QString::fromStdString(recipe.processor_id)); |
| 3109 | processor.setAttribute(QStringLiteral("output_name"), QString::fromStdString(recipe.output_name)); |
| 3110 | if (recipe.processor) { |
| 3111 | layout_xml::appendJsonAsCdata(doc, processor, QString::fromStdString(recipe.processor->saveParams())); |
| 3112 | } |
| 3113 | // Embed the filter's Luau source so the layout reopens on a machine without |
| 3114 | // this filter installed (resolved by restore's source_fallback leg). A named |
| 3115 | // child element, NOT a second direct CDATA child — restore reads params via |
| 3116 | // directCdataText, which ignores child elements. Empty for native C++ builtins. |
| 3117 | if (!recipe.filter_source.empty()) { |
| 3118 | QDomElement source_el = doc.createElement(QStringLiteral("source_fallback")); |
| 3119 | layout_xml::appendJsonAsCdata(doc, source_el, QString::fromStdString(recipe.filter_source)); |
| 3120 | processor.appendChild(source_el); |
| 3121 | } |
| 3122 | element.appendChild(processor); |
| 3123 | } |
| 3124 | // Plugin-created transforms (pj.data_processors.v1): named, owner-tagged nodes |
| 3125 | // that carry their script + bindings BY VALUE, so they replay on load WITHOUT the |
| 3126 | // originating plugin (needed only for re-editing). Persisted in the same snapshot |
| 3127 | // block as the per-curve filters above; restore re-installs them via |
| 3128 | // DataProcessorService::restoreTransform. Inputs/outputs are topic NAMES (the |
| 3129 | // engine resolves them on restore), so no (topic, field) rebinding is needed here. |
| 3130 | for (const auto& recipe : session_->sessionManager().dataProcessorService().transformRecipes()) { |
| 3131 | QDomElement transform = doc.createElement(QStringLiteral("transform")); |
| 3132 | transform.setAttribute(QStringLiteral("owner_plugin"), QString::fromStdString(recipe.owner_plugin)); |
| 3133 | transform.setAttribute(QStringLiteral("id"), QString::fromStdString(recipe.user_id)); |
| 3134 | transform.setAttribute(QStringLiteral("backend"), QString::fromStdString(recipe.backend)); |
| 3135 | transform.setAttribute(QStringLiteral("api_version"), QString::fromStdString(recipe.api_version)); |
| 3136 | if (!recipe.backend_version.empty()) { |
| 3137 | transform.setAttribute(QStringLiteral("backend_version"), QString::fromStdString(recipe.backend_version)); |
| 3138 | } |
| 3139 | for (const auto& input_name : recipe.inputs) { |
| 3140 | QDomElement in = doc.createElement(QStringLiteral("input")); |
| 3141 | in.setAttribute(QStringLiteral("name"), QString::fromStdString(input_name)); |
| 3142 | transform.appendChild(in); |
| 3143 | } |
| 3144 | for (const auto& output_name : recipe.outputs) { |
| 3145 | QDomElement out = doc.createElement(QStringLiteral("output")); |
| 3146 | out.setAttribute(QStringLiteral("name"), QString::fromStdString(output_name)); |
| 3147 | transform.appendChild(out); |
| 3148 | } |
| 3149 | // params (create(params), JSON) and script (the full backend payload, Luau today) |
nothing calls this directly
no test coverage detected