diff --git a/doc/release/notes/task-submit-operation.rst b/doc/release/notes/task-submit-operation.rst index 76390554fa4778fd4bfc6e55148c58fb952569ce..6c6890da584212f6c848ab10919876be2997d922 100644 --- a/doc/release/notes/task-submit-operation.rst +++ b/doc/release/notes/task-submit-operation.rst @@ -13,3 +13,13 @@ The operation parameter-editor panel responds to this new task by displaying the operation parameters corresponding to the task. See `smtk-pv-parameter-editor-panel`_ for more information. + + +An adaptor for configuring SubmitOperation tasks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There is a new :smtk:``smtk::task::adaptor::ConfigureOperation`` +class for situations where parts of an SMTK operation +managed by a :smtk:``smtk::task::SubmitOperation`` are +configured by an upstream :smtk:``smtk::task::FillOutAttributes`` +task. diff --git a/doc/userguide/task/adaptors.rst b/doc/userguide/task/adaptors.rst index 313c936ebb48da8feddaf2272ab9fa726e51960d..5369c2660d9eda8f33f744e04a82bcf26b585a07 100644 --- a/doc/userguide/task/adaptors.rst +++ b/doc/userguide/task/adaptors.rst @@ -80,3 +80,62 @@ Example "to": 2, /* must be a FillOutAttributes or Group task */ "to-tag": "foo" /* if "to" is a Group, "foo" is a key for resource+role data. */ } + + +ConfigureOperation +------------------ + +The :smtk:`ConfigureOperation ` +adaptor is used to automatically configure parts of an SMTK operation +assigned to a ``smtk::task::SubmitOperation`` task. +When the ``from`` task transitions to the ``Completable`` state, +this adaptor will copy values from attribute sets referenced in the +source task ( ``smtk::task::FillOutAttributes`` ) to operation parameters +in the destination task. And if the source task is active and in the +``Completable`` state, changes made to those attribute sets using the +smtk view system will also be forwarded. + +The JSON object to configure a ``ConfigureOperation`` adaptor adds a +``configure`` element to those defined for the base Adaptor class. +The ``configure`` element is a JSON array with each child element a +JSON object. + +* Each element in the ``configure`` array must include a ``from-role`` + element to + specify the attribute resource (matching the ``role`` in the + ``smtk::task::FillOutAttributes`` task). +* Additional key-value pairs specify the items to copy from + the attribute resource to the operation parameter instance. + + * Each key specifies one item in the attribute resource, using the + SMTK grammar syntax for search criteria. + These keys always start with the ``attribute[]`` syntax. + * Each value specifies the corresponding item in the operation + parameters. Because the parameter attribute is known to the + operation, the ``attribute[]`` syntax is omitted from this + part of the specification. + + +Example +"""""""" + +.. code:: json + + { + "id": 1, + "type": "smtk::task::adaptor::ConfigureOperation", + "from": 1, /* must be a FillOutAttributes task */ + "to": 2, /* must be a SubmitOperation or Group task */ + "configure": + [ + { + "from-role": "simulation attributes", + "attribute[type='BoxWidget']/box": "/bounds" + "attribute[type='BlockMeshSize']/MeshSize": "/meshsize", + "attribute[type='BlockMeshBoundaryConditions']/FrontBackSides": "/bc/frontback" + }, + { + ... + } + ] + } diff --git a/smtk/task/CMakeLists.txt b/smtk/task/CMakeLists.txt index 06b75ed3cbffc61ec4b412ff321ac030bab7fa08..c55d9370754e451ef2daae29719d008e8b9a0a75 100644 --- a/smtk/task/CMakeLists.txt +++ b/smtk/task/CMakeLists.txt @@ -9,6 +9,7 @@ set(tasks ) set(adaptors + ConfigureOperation ResourceAndRole ) diff --git a/smtk/task/Registrar.cxx b/smtk/task/Registrar.cxx index 402f5050f98d95b625086fcb0ad5871d82638866..1d8a93bdd6734f2dbce7e06399b25a206c163217 100644 --- a/smtk/task/Registrar.cxx +++ b/smtk/task/Registrar.cxx @@ -17,10 +17,12 @@ #include "smtk/task/Group.h" #include "smtk/task/SubmitOperation.h" #include "smtk/task/Task.h" +#include "smtk/task/adaptor/ConfigureOperation.h" #include "smtk/task/adaptor/ResourceAndRole.h" #include "smtk/task/json/Configurator.h" #include "smtk/task/json/Configurator.txx" #include "smtk/task/json/jsonAdaptor.h" +#include "smtk/task/json/jsonConfigureOperation.h" #include "smtk/task/json/jsonFillOutAttributes.h" #include "smtk/task/json/jsonGatherResources.h" #include "smtk/task/json/jsonGroup.h" @@ -44,8 +46,8 @@ using TaskJSON = std::tuple< json::jsonGatherResources, json::jsonGroup, json::jsonSubmitOperation>; -using AdaptorList = std::tuple; -using AdaptorJSON = std::tuple; +using AdaptorList = std::tuple; +using AdaptorJSON = std::tuple; void Registrar::registerTo(const smtk::task::Manager::Ptr& taskManager) { diff --git a/smtk/task/adaptor/ConfigureOperation.cxx b/smtk/task/adaptor/ConfigureOperation.cxx new file mode 100644 index 0000000000000000000000000000000000000000..dc386c8ad6cdc231001cd05d3aa1b3ab1fdacb6a --- /dev/null +++ b/smtk/task/adaptor/ConfigureOperation.cxx @@ -0,0 +1,393 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#include "smtk/task/adaptor/ConfigureOperation.h" + +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/ComponentItem.h" +#include "smtk/attribute/Item.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/operators/Signal.h" +#include "smtk/common/Managers.h" +#include "smtk/common/TypeName.h" +#include "smtk/io/Logger.h" +#include "smtk/operation/Manager.h" +#include "smtk/operation/Operation.h" +#include "smtk/resource/Manager.h" +#include "smtk/task/Manager.h" +#include "smtk/task/SubmitOperation.h" + +#include // std::move + +namespace +{ +auto& defaultLogger = smtk::io::Logger::instance(); +} + +namespace smtk +{ +namespace task +{ +namespace adaptor +{ + +ConfigureOperation::ConfigureOperation() = default; +ConfigureOperation::ConfigureOperation(const Configuration& config) +{ + this->configureSelf(config); +} + +ConfigureOperation::ConfigureOperation(const Configuration& config, Task* from, Task* to) + : Superclass(config, from, to) +{ + this->configureSelf(config); + + // Check state of "from" task + if (from == nullptr) + { + return; + } + + if (from->state() == smtk::task::State::Completable) + { + this->buildInternalData(); + this->setupAttributeObserver(); + m_applyChanges = true; + this->updateOperation(); + } + + // Add observer for "from" task state changes + m_taskObserver = from->observers().insert([this, config](Task&, State prev, State next) { + (void)prev; + bool isCompletable = (next == State::Completable); + if (isCompletable && m_attributeSet.empty()) + { + this->buildInternalData(); + this->setupAttributeObserver(); + m_applyChanges = true; + this->updateOperation(); + } + else + { + m_applyChanges = false; + } + }); +} + +bool ConfigureOperation::reconfigureTask() +{ + return false; // Required override +} + +void ConfigureOperation::configureSelf(const Configuration& config) +{ + auto configIter = config.find("configure"); + if (configIter == config.end()) + { + smtkWarningMacro(defaultLogger, "ConfigureOperation adaptor missing \"configure\" element."); + return; + } + else if (!configIter->is_array()) + { + smtkWarningMacro(defaultLogger, "ConfigureOperation \"configure\" element is not an array."); + return; + } + + for (auto it = configIter->begin(); it != configIter->end(); ++it) + { + if (!it->is_object()) + { + smtkWarningMacro( + defaultLogger, "ConfigureOperation \"configure\" has element that is not an object."); + continue; + } + + ParameterSet paramSet; + + // Traverse all items in the object + for (const auto& el : it->items()) + { + const std::string& key = el.key(); + const std::string value = el.value().get(); + if (key == "from-role") + { + paramSet.m_fromRole = value; + } + else + { + paramSet.m_pathMap[key] = value; + } + } // for (el) + + if (paramSet.m_fromRole.empty()) + { + smtkWarningMacro( + defaultLogger, "ConfigureOperation attribute-set missing \"from-role\" item."); + } + else + { + m_parameterSets.push_back(std::move(paramSet)); + } + } // for (configIter) +} + +bool ConfigureOperation::buildInternalData() +{ + defaultLogger.clearErrors(); + m_attributeSet.clear(); + m_itemTable.clear(); + + // "From" task - must be FillOutAttributes + auto* fromTask = dynamic_cast(this->from()); + if (fromTask == nullptr) + { + smtkErrorMacro( + defaultLogger, "ConfigureOperation \"from\" task is not type FillOutAttributes."); + return false; + } + + // "To" task - must be SubmitOperation + auto* operationTask = dynamic_cast(this->to()); + if (operationTask == nullptr) + { + smtkErrorMacro(defaultLogger, "ConfigureOperation \"to\" task is not SubmitOperation."); + return false; + } + + // Get the operation + auto* operation = operationTask->operation(); + if (operation == nullptr) + { + smtkErrorMacro( + defaultLogger, + "SubmitOperation task \"" << operationTask->title() + << "\" not configured to an Operation instance."); + return false; + } + + // Traverse parameter sets + for (const auto& paramSet : m_parameterSets) + { + // Find matching attribute set (gotta use visitor pattern, of course) + bool foundMatch = false; + fromTask->visitAttributeSets( + [this, &foundMatch, ¶mSet]( + const smtk::task::FillOutAttributes::AttributeSet& attSet) -> smtk::common::Visit { + if (attSet.m_role == paramSet.m_fromRole) + { + foundMatch = true; + this->updateInternalData(attSet, paramSet); + return smtk::common::Visit::Halt; + } + return smtk::common::Visit::Continue; + }); + + if (!foundMatch) + { + smtkErrorMacro( + defaultLogger, + "ConfigureOperation failed to find matching role \"" << paramSet.m_fromRole << "\""); + } + } + + bool ok = !defaultLogger.hasErrors(); + // Note: Now that m_itemTable is built (if there were no errors), we could clear m_paramterSets + return ok; +} + +bool ConfigureOperation::updateInternalData( + const smtk::task::FillOutAttributes::AttributeSet& attSet, + const ParameterSet& paramSet) +{ + // Get the resource manager + auto resManager = this->from()->manager()->managers()->get(); + + // Process each attribute resource in the attribute set + smtk::attribute::ResourcePtr attResource; + std::set atts; + for (const auto& el : attSet.m_resources) + { + smtk::common::UUID resUUID = el.first; + auto attResource = resManager->get(resUUID); + if (attResource == nullptr) + { + continue; + } + std::string role = attResource->properties().at("project_role"); + + // Populate atts + atts.clear(); + smtk::task::FillOutAttributes::ResourceAttributes resAtts = el.second; + std::set attUuids(resAtts.m_valid); + attUuids.insert(resAtts.m_invalid.begin(), resAtts.m_invalid.end()); + for (const auto& attUuid : attUuids) + { + auto att = attResource->findAttribute(attUuid); + if (att != nullptr) + { + atts.insert(att); + } + } + + // Process paramSet + for (const auto& it : paramSet.m_pathMap) + { + const std::string& attQuery = it.first; + const std::string& paramPath = it.second; + + // Split the attQuery into attribute[] and itemPath (is there an easier way?) + std::size_t n = attQuery.find(']'); + if (n == std::string::npos) + { + smtkErrorMacro( + defaultLogger, + "ConfigureOperation unexpected from spec \"" + << "\"; expected attribute[type='something']."); + continue; + } + std::string attributePart = attQuery.substr(0, n + 1); + std::string itemPart = attQuery.substr(n + 1); + if (itemPart[0] == '/') + { + itemPart = itemPart.substr(1); + } + + // Find the attribute & item in the FillOutAttribute tasks and assign to parameter + auto queryOp = attResource->queryOperation(attributePart); + for (const auto& fromAtt : atts) + { + if (queryOp(*fromAtt)) + { + m_attributeSet.insert(fromAtt->id()); + m_itemTable.emplace_back(fromAtt, itemPart, paramPath); + break; + } + } // for (fromAtt) + } // for (paramSet.m_pathMap) + } // for (el) + + bool ok = !defaultLogger.hasErrors(); + return ok; +} + +bool ConfigureOperation::setupAttributeObserver() +{ + // Get operation manager from managers + auto managers = this->from()->manager()->managers(); + auto opManager = managers->get(); + m_attributeObserver = opManager->observers().insert( + [this]( + const smtk::operation::Operation& op, + smtk::operation::EventType eventType, + smtk::operation::Operation::Result result) -> int { + // Most of the time we can ignore the op + if (!this->m_applyChanges) + { + return 0; + } + + if (eventType != smtk::operation::EventType::DID_OPERATE) + { + return 0; + } + + if (op.typeName() != smtk::common::typeName()) + { + return 0; + } + + // Get the "from" task and check its state + smtk::task::Task* fromTask = this->from(); + if ((fromTask == nullptr) || fromTask->state() != smtk::task::State::Completable) + { + return 0; + } + + // Check if "to" task is not completed + if (this->to()->state() == smtk::task::State::Completed) + { + return 0; + } + + // Check the result for any attributes in our set + auto compItem = result->findComponent("modified"); + for (std::size_t i = 0; i < compItem->numberOfValues(); ++i) + { + auto compId = compItem->value(i)->id(); + if (m_attributeSet.find(compId) != m_attributeSet.end()) + { + this->updateOperation(); + break; + } + } + + return 1; + }); + + return true; +} + +bool ConfigureOperation::updateOperation() const +{ + auto* operationTask = dynamic_cast(this->to()); + auto* operation = operationTask->operation(); + + // Traverse m_itemTable + for (const auto& t : m_itemTable) + { + const smtk::attribute::AttributePtr fromAtt = std::get<0>(t).lock(); + if (fromAtt == nullptr) + { + smtkWarningMacro(defaultLogger, "fromAtt is null"); + continue; + } + const std::string& fromItemPath = std::get<1>(t); + const std::string& paramItemPath = std::get<2>(t); + + auto fromItem = fromAtt->itemAtPath(fromItemPath); + if (fromItem == nullptr) + { + smtkWarningMacro( + defaultLogger, + "ConfigureOperation did not find attribute \" " << fromAtt->name() << "\" itemAtPath \"" + << fromItemPath << "\""); + continue; + } + + // Get the parameter + auto paramItem = operation->parameters()->itemAtPath(paramItemPath); + if (paramItem == nullptr) + { + smtkWarningMacro( + defaultLogger, + "ConfigureOperation did not find operation parameter at path \" 0" << paramItemPath + << "\""); + continue; + } + + // Use assign() method + smtk::attribute::CopyAssignmentOptions options; + bool assignOK = paramItem->assign(fromItem, options, defaultLogger); + if (!assignOK) + { + smtkWarningMacro( + defaultLogger, + "ConfigureOperation failed to assign parameter at path \" " << paramItemPath << "\""); + } + } + + // Update operation task state + operationTask->internalStateChanged(operationTask->computeInternalState()); + return true; +} + +} // namespace adaptor +} // namespace task +} // namespace smtk diff --git a/smtk/task/adaptor/ConfigureOperation.h b/smtk/task/adaptor/ConfigureOperation.h new file mode 100644 index 0000000000000000000000000000000000000000..54fee329d5c7f3e4b8ccc77e95097e6c019210ed --- /dev/null +++ b/smtk/task/adaptor/ConfigureOperation.h @@ -0,0 +1,91 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= + +#ifndef smtk_task_adaptor_ConfigureOperation_h +#define smtk_task_adaptor_ConfigureOperation_h + +#include "smtk/task/Adaptor.h" + +#include "smtk/PublicPointerDefs.h" +#include "smtk/common/UUID.h" +#include "smtk/task/FillOutAttributes.h" +#include "smtk/task/Task.h" + +#include +#include +#include +#include +#include + +namespace smtk +{ +namespace task +{ +namespace adaptor +{ + +/// Configure a task with a resource and role given a dependent producer. +class SMTKCORE_EXPORT ConfigureOperation : public Adaptor +{ +public: + smtkTypeMacro(smtk::task::adaptor::ConfigureOperation); + smtkSuperclassMacro(smtk::task::Adaptor); + smtkCreateMacro(smtk::task::Adaptor); + + struct ParameterSet + { + /// Role assigned to input attribute resource + std::string m_fromRole; + /// Map of + std::map m_pathMap; + }; + + /// Constructors + ConfigureOperation(); + ConfigureOperation(const Configuration& config); + ConfigureOperation(const Configuration& config, Task* from, Task* to); + + bool reconfigureTask() override; // required override + +protected: + void configureSelf(const Configuration& config); + + /// Builds m_attributeSet and m_itemTable + bool buildInternalData(); + bool updateInternalData(const smtk::task::FillOutAttributes::AttributeSet&, const ParameterSet&); + + /// Creates signal observer + bool setupAttributeObserver(); + + /// Copies items from FillOut task to SubmitOp task + bool updateOperation() const; + + /// Stores configuration data + std::vector m_parameterSets; + + /// Observers for task state changes and attribute changes + smtk::task::Task::Observers::Key m_taskObserver; + smtk::operation::Observers::Key m_attributeObserver; + + /// Stores attributes (uuids) that are to be checked for changes + std::set m_attributeSet; + + /// Table listing + std::vector> m_itemTable; + + /// Indicates when FillOut task is completable, which is when we apply changes + bool m_applyChanges = false; +}; + +} // namespace adaptor +} // namespace task +} // namespace smtk + +#endif // smtk_task_adaptor_ConfigureOperation_h diff --git a/smtk/task/json/Helper.cxx b/smtk/task/json/Helper.cxx index 8584b7a5bddeba9c9db5ea8e083aeb7478cbdefc..b97914133603944685fd90922e5d3cc183994bc6 100644 --- a/smtk/task/json/Helper.cxx +++ b/smtk/task/json/Helper.cxx @@ -162,6 +162,7 @@ Task::PassedDependencies Helper::unswizzleDependencies(const json& ids) const { smtkWarningMacro( smtk::io::Logger::instance(), "No task or null task for ID " << taskId << ". Skipping."); + continue; } deps.insert(ptr->shared_from_this()); } diff --git a/smtk/task/json/jsonConfigureOperation.cxx b/smtk/task/json/jsonConfigureOperation.cxx new file mode 100644 index 0000000000000000000000000000000000000000..2a114c269379f0102ac078237e4ea27a28b3d830 --- /dev/null +++ b/smtk/task/json/jsonConfigureOperation.cxx @@ -0,0 +1,42 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/task/json/jsonConfigureOperation.h" +#include "smtk/task/json/Helper.h" +#include "smtk/task/json/jsonAdaptor.h" + +#include "smtk/task/adaptor/ConfigureOperation.h" + +namespace smtk +{ +namespace task +{ + +// using adaptor::ConfigureOperation; + +namespace json +{ + +Adaptor::Configuration jsonConfigureOperation::operator()(const Adaptor* adaptor, Helper& helper) + const +{ + Adaptor::Configuration config; + auto* ncadaptor = const_cast(adaptor); + auto* ConfigureOperation = dynamic_cast(ncadaptor); + if (ConfigureOperation) + { + jsonAdaptor superclass; + config = superclass(ConfigureOperation, helper); + } + return config; +} + +} // namespace json +} // namespace task +} // namespace smtk diff --git a/smtk/task/json/jsonConfigureOperation.h b/smtk/task/json/jsonConfigureOperation.h new file mode 100644 index 0000000000000000000000000000000000000000..6002a1a0aea588df5f208ef0df9e92fb2b913ac3 --- /dev/null +++ b/smtk/task/json/jsonConfigureOperation.h @@ -0,0 +1,42 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#ifndef smtk_task_json_ConfigureOperation_h +#define smtk_task_json_ConfigureOperation_h + +#include "smtk/task/adaptor/ConfigureOperation.h" + +#include "nlohmann/json.hpp" + +#include +#include + +namespace smtk +{ +namespace task +{ +namespace json +{ + +class Helper; + +// NB: ConfigureOperation is serialized/deserialized by methods in smtk/task/json/jsonAdaptor.h +// that use the Configurator to swizzle/unswizzle pointers held by tasks. +// This file just declares a functor to fetch a ConfigureOperation from the Configurator. + +struct SMTKCORE_EXPORT jsonConfigureOperation +{ + Adaptor::Configuration operator()(const Adaptor* task, Helper& helper) const; +}; + +} // namespace json +} // namespace task +} // namespace smtk + +#endif // smtk_task_json_ConfigureOperation_h diff --git a/smtk/task/testing/cxx/CMakeLists.txt b/smtk/task/testing/cxx/CMakeLists.txt index 176e26dbc98faf90e06d75f29e742fe43a23ed28..11395171f6ab4dd472d9cce7c66cc2d5bc626e6c 100644 --- a/smtk/task/testing/cxx/CMakeLists.txt +++ b/smtk/task/testing/cxx/CMakeLists.txt @@ -1,5 +1,6 @@ set(unit_tests TestActiveTask.cxx + TestConfigureOperation.cxx TestTaskBasics.cxx TestTaskGroup.cxx TestTaskJSON.cxx diff --git a/smtk/task/testing/cxx/TestConfigureOperation.cxx b/smtk/task/testing/cxx/TestConfigureOperation.cxx new file mode 100644 index 0000000000000000000000000000000000000000..8b6e58f4ba47b8f924db04fae07c1d976d05aaa6 --- /dev/null +++ b/smtk/task/testing/cxx/TestConfigureOperation.cxx @@ -0,0 +1,366 @@ +//========================================================================= +// Copyright (c) Kitware, Inc. +// All rights reserved. +// See LICENSE.txt for details. +// +// This software is distributed WITHOUT ANY WARRANTY; without even +// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. See the above copyright notice for more information. +//========================================================================= +#include "smtk/attribute/Attribute.h" +#include "smtk/attribute/DoubleItem.h" +#include "smtk/attribute/Registrar.h" +#include "smtk/attribute/Resource.h" +#include "smtk/attribute/operators/Signal.h" +#include "smtk/common/Managers.h" +#include "smtk/io/AttributeReader.h" +#include "smtk/io/Logger.h" +#include "smtk/operation/Manager.h" +#include "smtk/operation/Operation.h" +#include "smtk/operation/Registrar.h" +#include "smtk/plugin/Registry.h" +#include "smtk/resource/Manager.h" +#include "smtk/resource/Registrar.h" +#include "smtk/task/GatherResources.h" +#include "smtk/task/Instances.h" +#include "smtk/task/Manager.h" +#include "smtk/task/Registrar.h" +#include "smtk/task/SubmitOperation.h" +#include "smtk/task/Task.h" + +#include "smtk/task/json/Helper.h" +#include "smtk/task/json/jsonManager.h" +#include "smtk/task/json/jsonTask.h" + +#include "smtk/common/testing/cxx/helpers.h" + +#include "nlohmann/json.hpp" + +#include +#include +#include +#include + +namespace +{ +std::string attTemplate = R"( + + + + + + 1.1 + + + + + + + -999.999 + + + + + + + + + +)"; + +std::string tasksConfig = R"( + { + "adaptors": [ + { + "from": 1, + "id": 1, + "to": 2, + "type": "smtk::task::adaptor::ResourceAndRole" + }, + { + "configure": [ + { + "attribute[type='source-att']/item1": "/parameter1", + "from-role": "attributes" + } + ], + "from": 2, + "id": 2, + "to": 3, + "type": "smtk::task::adaptor::ConfigureOperation" + } + ], + "styles": { + "operation_view": { + "operation-panel": { + "focus-task-operation": true + } + } + }, + "tasks": [ + { + "auto-configure": false, + "id": 1, + "resources": [ + { + "role": "attributes", + "type": "smtk::attribute::Resource", + "max": 1 + } + ], + "title": "Assign Attribute Resource", + "type": "smtk::task::GatherResources" + }, + { + "attribute-sets": [ + { + "definitions": [ + "source-att" + ], + "role": "attributes" + } + ], + "id": 2, + "title": "Edit Attributes", + "type": "smtk::task::FillOutAttributes" + }, + { + "id": 3, + "operation": "SimpleOperation", + "parameters": [], + "run-style": "smtk::task::SubmitOperation::RunStyle::OnCompletion", + "style": [ + "operation_view" + ], + "title": "Simple Operation", + "type": "smtk::task::SubmitOperation" + } + ] + } +)"; + +std::string simpleOpSpec = R"( + + + + + + + + + +)"; + +class SimpleOperation : public smtk::operation::Operation +{ +public: + smtkTypeMacro(SimpleOperation); + smtkCreateMacro(SimpleOperation); + smtkSharedFromThisMacro(smtk::operation::Operation); + + SimpleOperation() = default; + ~SimpleOperation() override = default; + + smtk::operation::Operation::Specification createSpecification() override + { + auto spec = this->createBaseSpecification(); + smtk::io::AttributeReader reader; + bool err = reader.readContents(spec, simpleOpSpec, this->log()); + smtkTest(!err, "Error creating SimpleOperation spec."); + return spec; + } + + Result operateInternal() override { return this->createResult(m_outcome); } + + Outcome m_outcome{ Outcome::SUCCEEDED }; +}; + +void checkTaskStates( + const std::vector& tasks, + const std::vector& expected) +{ + for (std::size_t i = 0; i < tasks.size(); ++i) + { + const smtk::task::Task::Ptr task = tasks[i]; + smtkTest( + task->state() == expected[i], + "Task " << task->title() << " expected state " << expected[i] << " actual state " + << task->state()); + } +} + +void printTaskStates( + const std::vector& tasks, + const std::string& note = std::string()) +{ + if (!note.empty()) + { + std::cout << note << '\n'; + } + + for (const auto& task : tasks) + { + std::cout << task->title() << " -- " << task->state() << '\n'; + } + + std::cout << std::flush; +} + +} // anonymous namespace + +int TestConfigureOperation(int, char*[]) +{ + std::cout << std::boolalpha; + auto& logger = smtk::io::Logger::instance(); + + // Create managers + auto managers = smtk::common::Managers::create(); + auto attributeRegistry = smtk::plugin::addToManagers(managers); + auto resourceRegistry = smtk::plugin::addToManagers(managers); + auto operationRegistry = smtk::plugin::addToManagers(managers); + auto taskRegistry = smtk::plugin::addToManagers(managers); + + auto resourceManager = managers->get(); + auto operationManager = managers->get(); + auto taskManager = smtk::task::Manager::create(); + taskManager->setManagers(managers); + + auto attributeResourceRegistry = + smtk::plugin::addToManagers(resourceManager); + auto attributeOperationRegistry = + smtk::plugin::addToManagers(operationManager); + auto taskTaskRegistry = smtk::plugin::addToManagers(taskManager); + + // Register the test operation + bool registered = operationManager->registerOperation("SimpleOperation"); + smtkTest(registered, "failed to register SimpleOperation"); + + // Create attribute resource + auto attResource = resourceManager->create(); + smtk::io::AttributeReader attReader; + bool err = attReader.readContents(attResource, attTemplate, logger); + smtkTest(!err, "failed to read attribute template"); + + smtkTest(attResource->hasAttributes(), "expected att resource to have attributes"); + // Verify that attribute was created + auto specAtt = attResource->findAttribute("source-att"); + smtkTest(specAtt != nullptr, "source-att attribute not found"); + smtkTest(specAtt->isValid(), "source-att attribute not valid"); + + resourceManager->add(attResource); + attResource->setName("attributes"); + attResource->properties().get()["project_role"] = "attributes"; + + // Add a second attribute resource with same role to make sure it isn't used + auto unusedAttResource = resourceManager->create(); + resourceManager->add(unusedAttResource); + unusedAttResource->setName("attributes"); + unusedAttResource->properties().get()["project_role"] = "attributes"; + + // Populate taskManager + auto config = nlohmann::json::parse(tasksConfig); + // std::cout << config.dump(2) << "\n"; + bool ok = true; + try + { + smtk::task::json::Helper::pushInstance(*taskManager, managers); + smtk::task::from_json(config, *taskManager); + smtk::task::json::Helper::popInstance(); + } + catch (std::exception&) + { + ok = false; + } + smtkTest(ok, "Failed to parse configuration."); + smtkTest( + taskManager->taskInstances().size() == 3, + "Expected to deserialize 3 tasks, not " << taskManager->taskInstances().size()); + + smtkTest( + taskManager->adaptorInstances().size() == 2, + "Expected to deserialize 2 adaptors, not " << taskManager->adaptorInstances().size()); + + // Organize tasks into std::vector + std::vector taskNames = { "Assign Attribute Resource", + "Edit Attributes", + "Simple Operation" }; + std::size_t numTasks = taskNames.size(); + std::vector tasks(numTasks); + bool hasErrors; + taskManager->taskInstances().visit( + [&tasks, &taskNames, &hasErrors](const smtk::task::Task::Ptr& task) { + bool found = false; + for (unsigned int i = 0; (i < taskNames.size()) || (!found); i++) + { + if (task->title() == taskNames[i]) + { + found = true; + tasks[i] = task; + } + } + if (!found) + { + std::cerr << "Found unexpected task: " << task->title() << std::endl; + hasErrors = true; + } + return smtk::common::Visit::Continue; + }); + + // Set attribute resource for GatherResources + auto gatherTask = std::dynamic_pointer_cast(tasks.front()); + smtkTest(gatherTask != nullptr, "failed to get GatherResources task"); + + // Check initial task states + printTaskStates(tasks, "\n*** Initial states:"); + std::vector initialExpected = { smtk::task::State::Incomplete, + smtk::task::State::Unavailable, + smtk::task::State::Incomplete }; + checkTaskStates(tasks, initialExpected); + + // Set the GatherResources' attribute resource + gatherTask->addResourceInRole(attResource, "attributes"); + + printTaskStates(tasks, "\n*** After GatherResources:"); + std::vector gatherExpected = { smtk::task::State::Completable, + smtk::task::State::Completable, + smtk::task::State::Completable }; + checkTaskStates(tasks, gatherExpected); + + // Check SubmitOperation content + auto submitTask = std::dynamic_pointer_cast(tasks.back()); + smtkTest(submitTask != nullptr, "failed to get SubmitOperation task"); + smtkTest(submitTask->operation()->ableToOperate(), "operation not able to operate"); + { + double expected = 1.1; + double value = submitTask->operation()->parameters()->findDouble("parameter1")->value(); + double diff = std::fabs(value - expected); + smtkTest(diff < 0.001, "expected parameter value to be " << expected << " not " << value); + } + + // Change the source item's value and emit Signal + auto specItem = specAtt->findDouble("item1"); + specItem->setValue(3.14159); + auto signal = operationManager->create(); + signal->parameters()->findComponent("modified")->appendValue(specAtt); + auto result = signal->operate(); + + // Task states should be the same but parameter value changed + checkTaskStates(tasks, gatherExpected); + { + double expected = 3.14159; + double value = submitTask->operation()->parameters()->findDouble("parameter1")->value(); + double diff = std::fabs(value - expected); + smtkTest(diff < 0.001, "expected parameter value to be " << expected << " not " << value); + } + + // Print any log messages + if (logger.numberOfRecords() > 0) + { + std::cout << "\nLog:\n" << logger.convertToString(true) << std::endl; + } + else + { + std::cout << "\n(Log is empty)\n" << std::endl; + } + return 0; +}