Skip to content

Commit

Permalink
[processing] Show more detail in history dialog
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed May 1, 2024
1 parent 09f6ca3 commit cbf3108
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
301 changes: 238 additions & 63 deletions src/gui/processing/qgsprocessinghistoryprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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() )
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/gui/processing/qgsprocessinghistoryprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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;

};

Expand Down

0 comments on commit cbf3108

Please sign in to comment.