mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 15:10:22 -05:00
Collect: list and dict inputs accept several connections of their item type. Members are labelled with their position or key, and can be reordered or renamed from the connection context menu. Map: Map Over / Edit Map / Remove Map on tasks. A mapped task is drawn as a stack with a "map over" subtitle, its items are edited in the property view (JSON, or selected cases, well paths or views), and per-item progress is shown during a run. Workflows using map or collect are no longer read-only. The run status no longer shows "Completed" after a successful run.
192 lines
8.1 KiB
C++
192 lines
8.1 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2026 Equinor ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RiuWorkflowJobRunner.h"
|
|
|
|
#include "RiaQStringFormatter.h"
|
|
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
|
|
#include <format>
|
|
|
|
namespace
|
|
{
|
|
constexpr char PROGRESS_PREFIX[] = "@@RI_WORKFLOW_EVENT@@";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RiuWorkflowJobRunner::RiuWorkflowJobRunner( const QString& label, QObject* parent )
|
|
: QObject( parent )
|
|
, m_label( label )
|
|
{
|
|
connect( &m_process, &QProcess::readyReadStandardOutput, this, &RiuWorkflowJobRunner::onReadyReadStdout );
|
|
connect( &m_process, &QProcess::readyReadStandardError, this, &RiuWorkflowJobRunner::onReadyReadStderr );
|
|
connect( &m_process, QOverload<int, QProcess::ExitStatus>::of( &QProcess::finished ), this, &RiuWorkflowJobRunner::onProcessFinished );
|
|
connect( &m_process, &QProcess::errorOccurred, this, &RiuWorkflowJobRunner::onProcessError );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::start( const QString& program, const QStringList& arguments, const QProcessEnvironment& env )
|
|
{
|
|
RiaLogging::info( QString( "Running %1" ).arg( m_label ).toStdString() );
|
|
RiaLogging::info( QString( "[%1] $ %2 %3" ).arg( m_label, program, arguments.join( ' ' ) ).toStdString() );
|
|
m_process.setProcessEnvironment( env );
|
|
m_process.start( program, arguments );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::cancel()
|
|
{
|
|
if ( m_process.state() == QProcess::NotRunning ) return;
|
|
m_cancelRequested = true;
|
|
m_process.terminate();
|
|
if ( !m_process.waitForFinished( 2000 ) ) m_process.kill();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RiuWorkflowJobRunner::isRunning() const
|
|
{
|
|
return m_process.state() != QProcess::NotRunning;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::onReadyReadStdout()
|
|
{
|
|
m_stdoutBuf.append( m_process.readAllStandardOutput() );
|
|
drainLines( m_stdoutBuf, RILogLevel::RI_LL_INFO );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::onReadyReadStderr()
|
|
{
|
|
m_stderrBuf.append( m_process.readAllStandardError() );
|
|
drainLines( m_stderrBuf, RILogLevel::RI_LL_WARNING );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::onProcessFinished( int exitCode, QProcess::ExitStatus status )
|
|
{
|
|
m_stdoutBuf.append( m_process.readAllStandardOutput() );
|
|
m_stderrBuf.append( m_process.readAllStandardError() );
|
|
|
|
if ( !m_stdoutBuf.endsWith( '\n' ) ) m_stdoutBuf.append( '\n' );
|
|
if ( !m_stderrBuf.endsWith( '\n' ) ) m_stderrBuf.append( '\n' );
|
|
drainLines( m_stdoutBuf, RILogLevel::RI_LL_INFO );
|
|
drainLines( m_stderrBuf, RILogLevel::RI_LL_WARNING );
|
|
|
|
if ( m_cancelRequested )
|
|
{
|
|
RiaLogging::info( std::format( "Cancelled {}", m_label ) );
|
|
}
|
|
else if ( status == QProcess::NormalExit && exitCode == 0 )
|
|
{
|
|
RiaLogging::info( QString( "Finished %1 (exit 0)" ).arg( m_label ).toStdString() );
|
|
}
|
|
else if ( status == QProcess::NormalExit )
|
|
{
|
|
RiaLogging::error( QString( "Finished %1 (exit %2)" ).arg( m_label ).arg( exitCode ).toStdString() );
|
|
}
|
|
else
|
|
{
|
|
RiaLogging::error( QString( "%1 crashed" ).arg( m_label ).toStdString() );
|
|
}
|
|
|
|
if ( !m_finishedEmitted )
|
|
{
|
|
m_finishedEmitted = true;
|
|
emit runFinished( status == QProcess::NormalExit && exitCode == 0 && !m_cancelRequested, m_cancelRequested );
|
|
}
|
|
deleteLater();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::onProcessError( QProcess::ProcessError error )
|
|
{
|
|
if ( error != QProcess::FailedToStart || m_finishedEmitted ) return;
|
|
m_finishedEmitted = true;
|
|
RiaLogging::error( std::format( "Failed to launch {}: {}", m_label, m_process.errorString() ) );
|
|
emit runFinished( false, false );
|
|
deleteLater();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RiuWorkflowJobRunner::drainLines( QByteArray& buffer, RILogLevel level )
|
|
{
|
|
int nl;
|
|
while ( ( nl = buffer.indexOf( '\n' ) ) >= 0 )
|
|
{
|
|
QString line = QString::fromUtf8( buffer.left( nl ) );
|
|
buffer.remove( 0, nl + 1 );
|
|
if ( line.endsWith( '\r' ) ) line.chop( 1 );
|
|
if ( line.isEmpty() ) continue;
|
|
|
|
if ( level == RILogLevel::RI_LL_INFO && line.startsWith( PROGRESS_PREFIX ) )
|
|
{
|
|
const QJsonDocument doc = QJsonDocument::fromJson( line.mid( sizeof( PROGRESS_PREFIX ) - 1 ).toUtf8() );
|
|
if ( doc.isObject() )
|
|
{
|
|
const QJsonObject event = doc.object();
|
|
if ( event.value( "event" ) == "task_state" && event.value( "run_id" ).isString() && event.value( "task" ).isString() &&
|
|
event.value( "state" ).isString() )
|
|
{
|
|
emit taskStateChanged( event.value( "run_id" ).toString(),
|
|
event.value( "task" ).toString(),
|
|
event.value( "state" ).toString(),
|
|
event.value( "error" ).toString() );
|
|
continue;
|
|
}
|
|
if ( event.value( "event" ) == "map_item_state" && event.value( "run_id" ).isString() && event.value( "task" ).isString() &&
|
|
event.value( "item" ).isString() && event.value( "state" ).isString() )
|
|
{
|
|
emit mapItemStateChanged( event.value( "run_id" ).toString(),
|
|
event.value( "task" ).toString(),
|
|
event.value( "item" ).toString(),
|
|
event.value( "state" ).toString(),
|
|
event.value( "error" ).toString() );
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string msg = QString( "[%1] %2" ).arg( m_label, line ).toStdString();
|
|
if ( level == RILogLevel::RI_LL_WARNING )
|
|
RiaLogging::warning( msg );
|
|
else
|
|
RiaLogging::info( msg );
|
|
}
|
|
}
|