[processing] Show more detail in history dialog

Use a tree display for processing history entries, where the root
item for each entry shows the full algorithm log when clicked,
and the python/qgis_process commands are instead shown as child
items

This provides more useful information for users browsing the history,
while still making the all the previous information available
This commit is contained in:
Nyall Dawson 2023-04-24 16:05:35 +10:00
parent 0360367b1e
commit e9976c2ee7
4 changed files with 244 additions and 64 deletions

View File

@ -35,6 +35,8 @@ This should only be called once -- calling multiple times will result in duplica
virtual QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) /Factory/;
virtual void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context );
signals:

View File

@ -35,6 +35,8 @@ This should only be called once -- calling multiple times will result in duplica
virtual QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) /Factory/;
virtual void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context );
signals:

View File

@ -22,6 +22,8 @@
#include "qgshistoryentrynode.h"
#include "qgsprocessingregistry.h"
#include "qgscodeeditorpython.h"
#include "qgscodeeditorshell.h"
#include "qgscodeeditorjson.h"
#include "qgsjsonutils.h"
#include <nlohmann/json.hpp>
@ -83,11 +85,12 @@ void QgsProcessingHistoryProvider::portOldLog()
///@cond PRIVATE
class ProcessingHistoryNode : public QgsHistoryEntryGroup
class ProcessingHistoryBaseNode : public QgsHistoryEntryGroup
{
public:
ProcessingHistoryNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
ProcessingHistoryBaseNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: mEntry( entry )
, mAlgorithmId( mEntry.entry.value( "algorithm_id" ).toString() )
, mPythonCommand( mEntry.entry.value( "python_command" ).toString() )
@ -100,65 +103,7 @@ class ProcessingHistoryNode : public QgsHistoryEntryGroup
{
const QVariantMap parametersMap = parameters.toMap();
mInputs = parametersMap.value( QStringLiteral( "inputs" ) ).toMap();
mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs );
}
else
{
// an older history entry which didn't record inputs
mDescription = mPythonCommand;
}
if ( mDescription.length() > 300 )
{
mDescription = QObject::tr( "%1…" ).arg( mDescription.left( 299 ) );
}
}
QVariant data( int role = Qt::DisplayRole ) const override
{
if ( mAlgorithmInformation.displayName.isEmpty() )
{
mAlgorithmInformation = QgsApplication::processingRegistry()->algorithmInformation( mAlgorithmId );
}
switch ( role )
{
case Qt::DisplayRole:
{
const QString algName = mAlgorithmInformation.displayName;
if ( !mDescription.isEmpty() )
return QStringLiteral( "[%1] %2 - %3" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName,
mDescription );
else
return QStringLiteral( "[%1] %2" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName );
}
case Qt::DecorationRole:
{
return mAlgorithmInformation.icon;
}
}
return QVariant();
}
QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorPython *codeEditor = new QgsCodeEditorPython( );
codeEditor->setReadOnly( true );
codeEditor->setCaretLineVisible( false );
codeEditor->setLineNumbersVisible( false );
codeEditor->setFoldingVisible( false );
codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
const QString introText = QStringLiteral( "\"\"\"\n%1\n\"\"\"\n\n " ).arg(
QObject::tr( "Double-click on the history item or paste the command below to re-run the algorithm" ) );
codeEditor->setText( introText + mPythonCommand );
return codeEditor;
}
bool doubleClicked( const QgsHistoryWidgetContext & ) override
@ -248,18 +193,248 @@ class ProcessingHistoryNode : public QgsHistoryEntryGroup
QString mPythonCommand;
QString mProcessCommand;
QVariantMap mInputs;
QString mDescription;
mutable QgsProcessingAlgorithmInformation mAlgorithmInformation;
QgsProcessingHistoryProvider *mProvider = nullptr;
};
class ProcessingHistoryPythonCommandNode : public ProcessingHistoryBaseNode
{
public:
ProcessingHistoryPythonCommandNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: ProcessingHistoryBaseNode( entry, provider )
{}
QVariant data( int role = Qt::DisplayRole ) const override
{
switch ( role )
{
case Qt::DisplayRole:
{
QString display = mPythonCommand;
if ( display.length() > 300 )
{
display = QObject::tr( "%1…" ).arg( display.left( 299 ) );
}
return display;
}
case Qt::DecorationRole:
return QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) );
default:
break;
}
return QVariant();
}
QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorPython *codeEditor = new QgsCodeEditorPython( );
codeEditor->setReadOnly( true );
codeEditor->setCaretLineVisible( false );
codeEditor->setLineNumbersVisible( false );
codeEditor->setFoldingVisible( false );
codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
const QString introText = QStringLiteral( "\"\"\"\n%1\n\"\"\"\n\n " ).arg(
QObject::tr( "Double-click on the history item or paste the command below to re-run the algorithm" ) );
codeEditor->setText( introText + mPythonCommand );
return codeEditor;
}
};
class ProcessingHistoryProcessCommandNode : public ProcessingHistoryBaseNode
{
public:
ProcessingHistoryProcessCommandNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: ProcessingHistoryBaseNode( entry, provider )
{}
QVariant data( int role = Qt::DisplayRole ) const override
{
switch ( role )
{
case Qt::DisplayRole:
{
QString display = mProcessCommand;
if ( display.length() > 300 )
{
display = QObject::tr( "%1…" ).arg( display.left( 299 ) );
}
return display;
}
case Qt::DecorationRole:
return QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) );
default:
break;
}
return QVariant();
}
QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorShell *codeEditor = new QgsCodeEditorShell( );
codeEditor->setReadOnly( true );
codeEditor->setCaretLineVisible( false );
codeEditor->setLineNumbersVisible( false );
codeEditor->setFoldingVisible( false );
codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
codeEditor->setText( mProcessCommand );
return codeEditor;
}
};
class ProcessingHistoryJsonNode : public ProcessingHistoryBaseNode
{
public:
ProcessingHistoryJsonNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: ProcessingHistoryBaseNode( entry, provider )
{
mJson = QString::fromStdString( QgsJsonUtils::jsonFromVariant( mInputs ).dump( 2 ) );
mJsonSingleLine = QString::fromStdString( QgsJsonUtils::jsonFromVariant( mInputs ).dump() );
}
QVariant data( int role = Qt::DisplayRole ) const override
{
switch ( role )
{
case Qt::DisplayRole:
{
QString display = mJsonSingleLine;
if ( display.length() > 300 )
{
display = QObject::tr( "%1…" ).arg( display.left( 299 ) );
}
return display;
}
case Qt::DecorationRole:
return QgsApplication::getThemeIcon( QStringLiteral( "mIconFieldJson.svg" ) );
default:
break;
}
return QVariant();
}
QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorJson *codeEditor = new QgsCodeEditorJson( );
codeEditor->setReadOnly( true );
codeEditor->setCaretLineVisible( false );
codeEditor->setLineNumbersVisible( false );
codeEditor->setFoldingVisible( false );
codeEditor->setEdgeMode( QsciScintilla::EdgeNone );
codeEditor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
codeEditor->setText( mJson );
return codeEditor;
}
QString mJson;
QString mJsonSingleLine;
};
class ProcessingHistoryRootNode : public ProcessingHistoryBaseNode
{
public:
ProcessingHistoryRootNode( const QgsHistoryEntry &entry, QgsProcessingHistoryProvider *provider )
: ProcessingHistoryBaseNode( entry, provider )
{
const QVariant parameters = mEntry.entry.value( QStringLiteral( "parameters" ) );
if ( parameters.type() == QVariant::Map )
{
mDescription = QgsProcessingUtils::variantToPythonLiteral( mInputs );
}
else
{
// an older history entry which didn't record inputs
mDescription = mPythonCommand;
}
if ( mDescription.length() > 300 )
{
mDescription = QObject::tr( "%1…" ).arg( mDescription.left( 299 ) );
}
addChild( new ProcessingHistoryPythonCommandNode( mEntry, mProvider ) );
addChild( new ProcessingHistoryProcessCommandNode( mEntry, mProvider ) );
addChild( new ProcessingHistoryJsonNode( mEntry, mProvider ) );
}
void setEntry( const QgsHistoryEntry &entry )
{
mEntry = entry;
}
QVariant data( int role = Qt::DisplayRole ) const override
{
if ( mAlgorithmInformation.displayName.isEmpty() )
{
mAlgorithmInformation = QgsApplication::processingRegistry()->algorithmInformation( mAlgorithmId );
}
switch ( role )
{
case Qt::DisplayRole:
{
const QString algName = mAlgorithmInformation.displayName;
if ( !mDescription.isEmpty() )
return QStringLiteral( "[%1] %2 - %3" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName,
mDescription );
else
return QStringLiteral( "[%1] %2" ).arg( mEntry.timestamp.toString( QStringLiteral( "yyyy-MM-dd hh:mm" ) ),
algName );
}
case Qt::DecorationRole:
{
return mAlgorithmInformation.icon;
}
default:
break;
}
return QVariant();
}
QString html( const QgsHistoryWidgetContext & ) const override
{
return mEntry.entry.value( QStringLiteral( "log" ) ).toString();
}
QString mDescription;
mutable QgsProcessingAlgorithmInformation mAlgorithmInformation;
};
///@endcond
QgsHistoryEntryNode *QgsProcessingHistoryProvider::createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & )
{
return new ProcessingHistoryNode( entry, this );
return new ProcessingHistoryRootNode( entry, this );
}
void QgsProcessingHistoryProvider::updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & )
{
if ( ProcessingHistoryRootNode *rootNode = dynamic_cast< ProcessingHistoryRootNode * >( node ) )
{
rootNode->setEntry( entry );
}
}
QString QgsProcessingHistoryProvider::oldLogPath() const

View File

@ -45,6 +45,7 @@ class GUI_EXPORT QgsProcessingHistoryProvider : public QgsAbstractHistoryProvide
void portOldLog();
QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override SIP_FACTORY;
void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override;
signals:
@ -72,7 +73,7 @@ class GUI_EXPORT QgsProcessingHistoryProvider : public QgsAbstractHistoryProvide
//! Returns the path to the old log file
QString oldLogPath() const;
friend class ProcessingHistoryNode;
friend class ProcessingHistoryBaseNode;
};