1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "WorkflowDesignerPlugin.h"
23 
24 #include <QDir>
25 #include <QMenu>
26 #include <QMessageBox>
27 
28 #include <U2Core/AppContext.h>
29 #include <U2Core/CMDLineHelpProvider.h>
30 #include <U2Core/CMDLineRegistry.h>
31 #include <U2Core/CMDLineUtils.h>
32 #include <U2Core/FileAndDirectoryUtils.h>
33 #include <U2Core/GAutoDeleteList.h>
34 #include <U2Core/L10n.h>
35 #include <U2Core/ServiceTypes.h>
36 #include <U2Core/Settings.h>
37 #include <U2Core/Task.h>
38 #include <U2Core/TaskStarter.h>
39 #include <U2Core/U2SafePoints.h>
40 
41 #include <U2Designer/DashboardInfoRegistry.h>
42 
43 #include <U2Gui/ToolsMenu.h>
44 
45 #include <U2Lang/IncludedProtoFactory.h>
46 #include <U2Lang/WorkflowEnv.h>
47 #include <U2Lang/WorkflowSettings.h>
48 #include <U2Lang/WorkflowTasksRegistry.h>
49 
50 #include <U2Test/GTest.h>
51 #include <U2Test/GTestFrameworkComponents.h>
52 
53 #include "WorkflowDocument.h"
54 #include "WorkflowSamples.h"
55 #include "WorkflowSettingsController.h"
56 #include "WorkflowViewController.h"
57 #include "cmdline/GalaxyConfigTask.h"
58 #include "cmdline/WorkflowCMDLineTasks.h"
59 #include "library/CoreLib.h"
60 #include "library/IncludedProtoFactoryImpl.h"
61 #include "tasks/ReadAssemblyTask.h"
62 #include "util/DatasetsCountValidator.h"
63 
64 namespace U2 {
65 
U2_PLUGIN_INIT_FUNC()66 extern "C" Q_DECL_EXPORT Plugin *U2_PLUGIN_INIT_FUNC() {
67     WorkflowDesignerPlugin *plug = new WorkflowDesignerPlugin();
68     return plug;
69 }
70 
71 const QString WorkflowDesignerPlugin::RUN_WORKFLOW = "task";
72 const QString WorkflowDesignerPlugin::PRINT = "print";
73 const QString WorkflowDesignerPlugin::CUSTOM_EL_WITH_SCRIPTS_DIR = "custom-element-script-dir";
74 const QString WorkflowDesignerPlugin::CUSTOM_EXTERNAL_TOOL_DIR = "custom-element-external-tool-dir";
75 const QString WorkflowDesignerPlugin::INCLUDED_ELEMENTS_DIR = "imported-workflow-element-dir";
76 const QString WorkflowDesignerPlugin::WORKFLOW_OUTPUT_DIR = "workflow-output-dir";
77 
WorkflowDesignerPlugin()78 WorkflowDesignerPlugin::WorkflowDesignerPlugin()
79     : Plugin(tr("Workflow Designer"), tr("Workflow Designer allows one to create complex computational workflows.")) {
80     if (AppContext::getMainWindow()) {
81         services << new WorkflowDesignerService();
82         AppContext::getAppSettingsGUI()->registerPage(new WorkflowSettingsPageController());
83         AppContext::getObjectViewFactoryRegistry()->registerGObjectViewFactory(new WorkflowViewFactory(this));
84     }
85     IncludedProtoFactory::init(new IncludedProtoFactoryImpl());
86 
87     AppContext::getDocumentFormatRegistry()->registerFormat(new WorkflowDocFormat(this));
88 
89     // xml workflow tests removed. commented for future uses
90 
91     // GTestFormatRegistry* tfr = AppContext::getTestFramework()->getTestFormatRegistry();
92     // XMLTestFormat *xmlTestFormat = qobject_cast<XMLTestFormat*>(tfr->findFormat("XML"));
93     // assert(xmlTestFormat!=NULL);
94 
95     // GAutoDeleteList<XMLTestFactory>* l = new GAutoDeleteList<XMLTestFactory>(this);
96     // l->qlist = WorkflowTests::createTestFactories();
97 
98     // foreach(XMLTestFactory* f, l->qlist) {
99     //     bool res = xmlTestFormat->registerTestFactory(f);
100     //     assert(res);
101     //     Q_UNUSED(res);
102     // }
103 
104     registerCMDLineHelp();
105     processCMDLineOptions();
106     WorkflowEnv::getActorValidatorRegistry()->addValidator(DatasetsCountValidator::ID, new DatasetsCountValidator());
107 
108     CHECK(AppContext::getPluginSupport(), );
109     connect(AppContext::getPluginSupport(), SIGNAL(si_allStartUpPluginsLoaded()), SLOT(sl_initWorkers()));
110 
111     DashboardInfoRegistry *dashboardsInfoRegistry = AppContext::getDashboardInfoRegistry();
112     SAFE_POINT(nullptr != dashboardsInfoRegistry, "dashboardsInfoRegistry is nullptr", );
113     AppContext::getDashboardInfoRegistry()->scanDashboardsDir();
114 }
115 
processCMDLineOptions()116 void WorkflowDesignerPlugin::processCMDLineOptions() {
117     CMDLineRegistry *cmdlineReg = AppContext::getCMDLineRegistry();
118     assert(cmdlineReg != nullptr);
119 
120     if (cmdlineReg->hasParameter(CUSTOM_EL_WITH_SCRIPTS_DIR)) {
121         WorkflowSettings::setUserDirectory(FileAndDirectoryUtils::getAbsolutePath(cmdlineReg->getParameterValue(CUSTOM_EL_WITH_SCRIPTS_DIR)));
122     }
123     if (cmdlineReg->hasParameter(CUSTOM_EXTERNAL_TOOL_DIR)) {
124         WorkflowSettings::setExternalToolDirectory(FileAndDirectoryUtils::getAbsolutePath(cmdlineReg->getParameterValue(CUSTOM_EXTERNAL_TOOL_DIR)));
125     }
126     if (cmdlineReg->hasParameter(INCLUDED_ELEMENTS_DIR)) {
127         WorkflowSettings::setIncludedElementsDirectory(FileAndDirectoryUtils::getAbsolutePath(cmdlineReg->getParameterValue(INCLUDED_ELEMENTS_DIR)));
128     }
129     if (cmdlineReg->hasParameter(WORKFLOW_OUTPUT_DIR)) {
130         WorkflowSettings::setWorkflowOutputDirectory(FileAndDirectoryUtils::getAbsolutePath(cmdlineReg->getParameterValue(WORKFLOW_OUTPUT_DIR)));
131     }
132 
133     bool consoleMode = !AppContext::isGUIMode();  // only in console mode we run workflows by default. Otherwise we show them
134     if (cmdlineReg->hasParameter(RUN_WORKFLOW) || (consoleMode && !CMDLineRegistryUtils::getPureValues().isEmpty())) {
135         Task *t = new WorkflowRunFromCMDLineTask();
136         connect(AppContext::getTaskScheduler(), SIGNAL(si_ugeneIsReadyToWork()), new TaskStarter(t), SLOT(registerTask()));
137     } else {
138         if (cmdlineReg->hasParameter(GalaxyConfigTask::GALAXY_CONFIG_OPTION) && consoleMode) {
139             const QString schemePath = cmdlineReg->getParameterValue(GalaxyConfigTask::GALAXY_CONFIG_OPTION);
140             const QString ugenePath = cmdlineReg->getParameterValue(GalaxyConfigTask::UGENE_PATH_OPTION);
141             const QString galaxyPath = cmdlineReg->getParameterValue(GalaxyConfigTask::GALAXY_PATH_OPTION);
142             const QString destinationPath = nullptr;
143             Task *t = new GalaxyConfigTask(schemePath, ugenePath, galaxyPath, destinationPath);
144             connect(AppContext::getPluginSupport(), SIGNAL(si_allStartUpPluginsLoaded()), new TaskStarter(t), SLOT(registerTask()));
145         }
146     }
147 }
148 
registerWorkflowTasks()149 void WorkflowDesignerPlugin::registerWorkflowTasks() {
150     WorkflowTasksRegistry *registry = WorkflowEnv::getWorkflowTasksRegistry();
151 
152     ReadDocumentTaskFactory *readAssemblyFactory = new ReadAssemblyTaskFactory();
153     bool ok = registry->registerReadDocumentTaskFactory(readAssemblyFactory);
154     if (!ok) {
155         coreLog.error("Can not register read assembly task");
156     }
157 }
158 
registerCMDLineHelp()159 void WorkflowDesignerPlugin::registerCMDLineHelp() {
160     CMDLineRegistry *cmdLineRegistry = AppContext::getCMDLineRegistry();
161     assert(nullptr != cmdLineRegistry);
162 
163     CMDLineHelpProvider *taskSection = new CMDLineHelpProvider(
164         RUN_WORKFLOW,
165         tr("Runs the specified task."),
166         tr("Runs the specified task. A path to a user-defined UGENE workflow"
167            " be used as a task name."),
168         tr("<task_name> [<task_parameter>=value ...]"));
169 
170     cmdLineRegistry->registerCMDLineHelpProvider(taskSection);
171 
172     CMDLineHelpProvider *printSection = new CMDLineHelpProvider(
173         PRINT,
174         tr("Prints the content of the specified slot."),
175         tr("Prints the content of the specified slot. The incoming/outcoming content of"
176            " specified slot is printed to the standard output."),
177         tr("<actor_name>.<port_name>.<slot_name>"));
178     Q_UNUSED(printSection);
179 
180     CMDLineHelpProvider *galaxyConfigSection = new CMDLineHelpProvider(
181         GalaxyConfigTask::GALAXY_CONFIG_OPTION,
182         tr("Creates new Galaxy tool config."),
183         tr("Creates new Galaxy tool config from existing workflow. Paths to UGENE"
184            " and Galaxy can be set"),
185         tr("<uwl-file> [--ugene-path=value] [--galaxy-path=value]"));
186 
187     cmdLineRegistry->registerCMDLineHelpProvider(galaxyConfigSection);
188 
189     // CMDLineHelpProvider * remoteMachineSectionArguments = new CMDLineHelpProvider( REMOTE_MACHINE, "<path-to-machine-file>");
190     // CMDLineHelpProvider * remoteMachineSection = new CMDLineHelpProvider( REMOTE_MACHINE, tr("run provided tasks on given remote machine") );
191     // TODO: bug UGENE-23
192     // cmdLineRegistry->registerCMDLineHelpProvider( remoteMachineSectionArguments );
193     // cmdLineRegistry->registerCMDLineHelpProvider( remoteMachineSection );
194 }
195 
sl_initWorkers()196 void WorkflowDesignerPlugin::sl_initWorkers() {
197     Workflow::CoreLib::init();
198     registerWorkflowTasks();
199     Workflow::CoreLib::initIncludedWorkers();
200 }
201 
~WorkflowDesignerPlugin()202 WorkflowDesignerPlugin::~WorkflowDesignerPlugin() {
203     Workflow::CoreLib::cleanup();
204 }
205 
206 class CloseDesignerTask : public Task {
207 public:
CloseDesignerTask(WorkflowDesignerService * s)208     CloseDesignerTask(WorkflowDesignerService *s)
209         : Task(U2::WorkflowDesignerPlugin::tr("Close Designer"), TaskFlag_NoRun),
210           service(s) {
211     }
212     virtual void prepare();
213 
214 private:
215     WorkflowDesignerService *service;
216 };
217 
prepare()218 void CloseDesignerTask::prepare() {
219     if (!service->closeViews()) {
220         stateInfo.setError(U2::WorkflowDesignerPlugin::tr("Close Designer canceled"));
221     }
222 }
223 
createServiceDisablingTask()224 Task *WorkflowDesignerService::createServiceDisablingTask() {
225     return new CloseDesignerTask(this);
226 }
227 
WorkflowDesignerService()228 WorkflowDesignerService::WorkflowDesignerService()
229     : Service(Service_WorkflowDesigner, tr("Workflow Designer"), ""),
230       designerAction(nullptr), managerAction(nullptr), newWorkflowAction(nullptr) {
231 }
232 
serviceStateChangedCallback(ServiceState,bool enabledStateChanged)233 void WorkflowDesignerService::serviceStateChangedCallback(ServiceState, bool enabledStateChanged) {
234     IdRegistry<WelcomePageAction> *welcomePageActions = AppContext::getWelcomePageActionRegistry();
235     SAFE_POINT(nullptr != welcomePageActions, L10N::nullPointerError("Welcome Page Actions"), );
236 
237     if (!enabledStateChanged) {
238         return;
239     }
240     if (isEnabled()) {
241         SAFE_POINT(nullptr == designerAction, "Illegal WD service state", );
242         SAFE_POINT(nullptr == newWorkflowAction, "Illegal WD service state", );
243 
244         if (!AppContext::getPluginSupport()->isAllPluginsLoaded()) {
245             connect(AppContext::getPluginSupport(), SIGNAL(si_allStartUpPluginsLoaded()), SLOT(sl_startWorkflowPlugin()));
246         } else {
247             sl_startWorkflowPlugin();
248         }
249 
250         welcomePageActions->registerEntry(new WorkflowWelcomePageAction(this));
251     } else {
252         welcomePageActions->unregisterEntry(BaseWelcomePageActions::CREATE_WORKFLOW);
253         delete newWorkflowAction;
254         newWorkflowAction = nullptr;
255         delete designerAction;
256         designerAction = nullptr;
257     }
258 }
259 
sl_startWorkflowPlugin()260 void WorkflowDesignerService::sl_startWorkflowPlugin() {
261     initDesignerAction();
262     initNewWorkflowAction();
263     initSampleActions();
264 }
265 
initDesignerAction()266 void WorkflowDesignerService::initDesignerAction() {
267     designerAction = new QAction(QIcon(":/workflow_designer/images/wd.png"), tr("Workflow Designer..."), this);
268     designerAction->setObjectName(ToolsMenu::WORKFLOW_DESIGNER);
269 #ifdef _DEBUG
270     designerAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_D));
271 #endif
272     connect(designerAction, SIGNAL(triggered()), SLOT(sl_showDesignerWindow()));
273     ToolsMenu::addAction(ToolsMenu::TOOLS, designerAction);
274 }
275 
initNewWorkflowAction()276 void WorkflowDesignerService::initNewWorkflowAction() {
277     newWorkflowAction = new QAction(QIcon(":/workflow_designer/images/wd.png"), tr("New workflow..."), this);
278     newWorkflowAction->setObjectName("New workflow");
279     connect(newWorkflowAction, SIGNAL(triggered()), SLOT(sl_showDesignerWindow()));
280 
281     QMenu *fileMenu = AppContext::getMainWindow()->getTopLevelMenu(MWMENU_FILE);
282     QAction *beforeAction = nullptr;
283     foreach (QAction *action, fileMenu->actions()) {
284         if (action->objectName() == ACTION_PROJECTSUPPORT__NEW_SECTION_SEPARATOR) {
285             beforeAction = action;
286             break;
287         }
288     }
289     fileMenu->insertAction(beforeAction, newWorkflowAction);
290 }
291 
closeViews()292 bool WorkflowDesignerService::closeViews() {
293     MWMDIManager *wm = AppContext::getMainWindow()->getMDIManager();
294     assert(wm);
295     foreach (MWMDIWindow *w, wm->getWindows()) {
296         WorkflowView *view = qobject_cast<WorkflowView *>(w);
297         if (view) {
298             if (!AppContext::getMainWindow()->getMDIManager()->closeMDIWindow(view)) {
299                 return false;
300             }
301         }
302     }
303     return true;
304 }
305 
checkServiceState() const306 bool WorkflowDesignerService::checkServiceState() const {
307     if (isDisabled()) {
308         QMessageBox::warning(QApplication::activeWindow(), L10N::warningTitle(), L10N::internalError(tr("Can not open Workflow Designer. Please, try to reload UGENE.")));
309         return false;
310     }
311     return true;
312 }
313 
sl_showDesignerWindow()314 void WorkflowDesignerService::sl_showDesignerWindow() {
315     CHECK(checkServiceState(), );
316     WorkflowView::openWD(nullptr);  // FIXME
317 }
318 
sl_sampleActionClicked(const SampleAction & action)319 void WorkflowDesignerService::sl_sampleActionClicked(const SampleAction &action) {
320     CHECK(checkServiceState(), );
321 
322     WorkflowView *view = WorkflowView::openWD(nullptr);
323     CHECK(nullptr != view, );
324 
325     view->sl_loadScene(QDir("data:workflow_samples").path() + "/" + action.samplePath, false);
326 }
327 
createServiceEnablingTask()328 Task *WorkflowDesignerService::createServiceEnablingTask() {
329     QString defaultDir = QDir::searchPaths(PATH_PREFIX_DATA).first() + "/workflow_samples";
330 
331     return SampleRegistry::init(QStringList(defaultDir));
332 }
333 
initSampleActions()334 void WorkflowDesignerService::initSampleActions() {
335     SampleActionsManager *samples = new SampleActionsManager(this);
336     connect(samples, SIGNAL(si_clicked(const SampleAction &)), SLOT(sl_sampleActionClicked(const SampleAction &)));
337 
338     const QString externalToolsPlugin = "external_tool_support";
339 
340     SampleAction ngsControl(ToolsMenu::NGS_CONTROL, ToolsMenu::NGS_MENU, "NGS/fastqc.uwl", tr("Reads quality control..."));
341     ngsControl.requiredPlugins << externalToolsPlugin;
342 
343     // SPAdes is available only on Linux and Mac.
344 #if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
345     SampleAction ngsDenovo(ToolsMenu::NGS_DENOVO, ToolsMenu::NGS_MENU, "NGS/from_tools_menu_only/ngs_assembly.uwl", tr("Reads de novo assembly (with SPAdes)..."));
346     ngsDenovo.requiredPlugins << externalToolsPlugin;
347     samples->registerAction(ngsDenovo);
348 #endif
349     SampleAction ngsScaffold(ToolsMenu::NGS_SCAFFOLD, ToolsMenu::NGS_MENU, "Scenarios/length_filter.uwl", tr("Filter short scaffolds..."));
350     ngsScaffold.requiredPlugins << externalToolsPlugin;
351     SampleAction ngsRawDna(ToolsMenu::NGS_RAW_DNA, ToolsMenu::NGS_MENU, "NGS/raw_dna.uwl", tr("Raw DNA-Seq data processing..."));
352     ngsRawDna.requiredPlugins << externalToolsPlugin;
353     SampleAction ngsVariants(ToolsMenu::NGS_CALL_VARIANTS, ToolsMenu::NGS_MENU, "NGS/ngs_variant_calling.uwl", tr("Variant calling..."));
354     ngsVariants.requiredPlugins << externalToolsPlugin;
355     SampleAction ngsEffect(ToolsMenu::NGS_VARIANT_EFFECT, ToolsMenu::NGS_MENU, "NGS/ngs_variant_annotation.uwl", tr("Annotate variants and predict effects..."));
356     ngsEffect.requiredPlugins << externalToolsPlugin;
357     SampleAction ngsRawRna(ToolsMenu::NGS_RAW_RNA, ToolsMenu::NGS_MENU, "NGS/raw_rna.uwl", tr("Raw RNA-Seq data processing..."));
358     ngsRawRna.requiredPlugins << externalToolsPlugin;
359     SampleAction ngsRna(ToolsMenu::NGS_RNA, ToolsMenu::NGS_MENU, "NGS/ngs_transcriptomics_tophat_stringtie.uwl", tr("RNA-Seq data analysis..."));
360     ngsRna.requiredPlugins << externalToolsPlugin;
361     SampleAction ngsTranscript(ToolsMenu::NGS_TRANSCRIPT, ToolsMenu::NGS_MENU, "NGS/extract_transcript_seq.uwl", tr("Extract transcript sequences..."));
362     ngsTranscript.requiredPlugins << externalToolsPlugin;
363     SampleAction ngsRawChip(ToolsMenu::NGS_RAW_CHIP, ToolsMenu::NGS_MENU, "NGS/raw_chip.uwl", tr("Raw ChIP-Seq data processing..."));
364     ngsRawChip.requiredPlugins << externalToolsPlugin;
365     SampleAction ngsChip(ToolsMenu::NGS_CHIP, ToolsMenu::NGS_MENU, "NGS/cistrome.uwl", tr("ChIP-Seq data analysis..."));
366     ngsChip.requiredPlugins << externalToolsPlugin;
367     SampleAction ngsClassification(ToolsMenu::NGS_CLASSIFICATION, ToolsMenu::NGS_MENU, "NGS/from_tools_menu_only/ngs_classification.uwl", tr("Metagenomics classification..."));
368     ngsChip.requiredPlugins << externalToolsPlugin << "kraken_support"
369                             << "clark_support"
370                             << "diamond_support"
371                             << "wevote_support"
372                             << "ngs_reads_classification";
373     SampleAction ngsCoverage(ToolsMenu::NGS_COVERAGE, ToolsMenu::NGS_MENU, "NGS/extract_coverage.uwl", tr("Extract coverage from assemblies..."));
374     ngsCoverage.requiredPlugins << externalToolsPlugin;
375     SampleAction ngsConsensus(ToolsMenu::NGS_CONSENSUS, ToolsMenu::NGS_MENU, "NGS/consensus.uwl", tr("Extract consensus from assemblies..."));
376     ngsConsensus.requiredPlugins << externalToolsPlugin;
377 
378     SampleAction blastNcbi(ToolsMenu::BLAST_NCBI, ToolsMenu::BLAST_MENU, "Scenarios/remote_blasting.uwl", tr("Remote NCBI BLAST..."));
379     blastNcbi.requiredPlugins << "remote_blast";
380 
381     samples->registerAction(ngsControl);
382     samples->registerAction(ngsScaffold);
383     samples->registerAction(ngsRawDna);
384     samples->registerAction(ngsVariants);
385     samples->registerAction(ngsEffect);
386     samples->registerAction(ngsRawRna);
387     samples->registerAction(ngsRna);
388     samples->registerAction(ngsTranscript);
389     samples->registerAction(ngsRawChip);
390     samples->registerAction(ngsChip);
391     samples->registerAction(ngsClassification);
392     samples->registerAction(ngsCoverage);
393     samples->registerAction(ngsConsensus);
394     samples->registerAction(blastNcbi);
395 }
396 
397 /************************************************************************/
398 /* WorkflowWelcomePageAction */
399 /************************************************************************/
WorkflowWelcomePageAction(WorkflowDesignerService * service)400 WorkflowWelcomePageAction::WorkflowWelcomePageAction(WorkflowDesignerService *service)
401     : WelcomePageAction(BaseWelcomePageActions::CREATE_WORKFLOW), service(service) {
402 }
403 
perform()404 void WorkflowWelcomePageAction::perform() {
405     SAFE_POINT(!service.isNull(), L10N::nullPointerError("Workflow Service"), );
406     service->sl_showDesignerWindow();
407 }
408 
409 }  // namespace U2
410