mirror of
https://github.com/OPM/ResInsight.git
synced 2025-02-25 18:55:39 -06:00
#10358 Regression Analysis: Allow selecting subset of time range for regression
This commit is contained in:
parent
5bd492dc56
commit
f1794abff2
@ -90,6 +90,7 @@ RimSummaryRegressionAnalysisCurve*
|
||||
|
||||
summaryPlot->addCurveAndUpdate( newCurve );
|
||||
|
||||
newCurve->updateDefaultValues();
|
||||
newCurve->loadDataAndUpdate( true );
|
||||
newCurve->updateConnectedEditors();
|
||||
|
||||
|
@ -708,6 +708,8 @@ void RimSummaryCurve::onLoadDataAndUpdate( bool updateParentPlot )
|
||||
{
|
||||
shouldPopulateViewWithEmptyData = true;
|
||||
}
|
||||
|
||||
updateTimeAnnotations();
|
||||
}
|
||||
|
||||
if ( shouldPopulateViewWithEmptyData )
|
||||
@ -1293,3 +1295,10 @@ void RimSummaryCurve::calculateCurveInterpolationFromAddress()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryCurve::updateTimeAnnotations()
|
||||
{
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ protected:
|
||||
|
||||
virtual std::vector<time_t> timeStepsX() const;
|
||||
|
||||
virtual void updateTimeAnnotations();
|
||||
|
||||
// Overridden PDM methods
|
||||
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
|
@ -1345,29 +1345,29 @@ void RimSummaryPlot::updateCaseNameHasChanged()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryPlot::addTimeAnnotation( time_t time )
|
||||
RimTimeAxisAnnotation* RimSummaryPlot::addTimeAnnotation( time_t time )
|
||||
{
|
||||
RimSummaryTimeAxisProperties* axisProps = timeAxisProperties();
|
||||
{
|
||||
auto* annotation = new RimTimeAxisAnnotation;
|
||||
annotation->setTime( time );
|
||||
|
||||
axisProps->appendAnnotation( annotation );
|
||||
}
|
||||
auto* annotation = new RimTimeAxisAnnotation;
|
||||
annotation->setTime( time );
|
||||
|
||||
axisProps->appendAnnotation( annotation );
|
||||
return annotation;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryPlot::addTimeRangeAnnotation( time_t startTime, time_t endTime )
|
||||
RimTimeAxisAnnotation* RimSummaryPlot::addTimeRangeAnnotation( time_t startTime, time_t endTime )
|
||||
{
|
||||
RimSummaryTimeAxisProperties* axisProps = timeAxisProperties();
|
||||
{
|
||||
auto* annotation = new RimTimeAxisAnnotation;
|
||||
annotation->setTimeRange( startTime, endTime );
|
||||
|
||||
axisProps->appendAnnotation( annotation );
|
||||
}
|
||||
auto* annotation = new RimTimeAxisAnnotation;
|
||||
annotation->setTimeRange( startTime, endTime );
|
||||
|
||||
axisProps->appendAnnotation( annotation );
|
||||
return annotation;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -1379,6 +1379,15 @@ void RimSummaryPlot::removeAllTimeAnnotations()
|
||||
axisProps->removeAllAnnotations();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryPlot::removeTimeAnnotation( RimTimeAxisAnnotation* annotation )
|
||||
{
|
||||
RimSummaryTimeAxisProperties* axisProps = timeAxisProperties();
|
||||
axisProps->removeAnnotation( annotation );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@ -60,6 +60,7 @@ class RimSummaryNameHelper;
|
||||
class RimSummaryPlotNameHelper;
|
||||
class RimPlotTemplateFileItem;
|
||||
class RimSummaryPlotSourceStepping;
|
||||
class RimTimeAxisAnnotation;
|
||||
class RiaSummaryCurveDefinition;
|
||||
|
||||
class QwtInterval;
|
||||
@ -122,9 +123,10 @@ public:
|
||||
void reattachAllCurves() override;
|
||||
void updateCaseNameHasChanged();
|
||||
|
||||
void addTimeAnnotation( time_t time );
|
||||
void addTimeRangeAnnotation( time_t startTime, time_t endTime );
|
||||
void removeAllTimeAnnotations();
|
||||
RimTimeAxisAnnotation* addTimeAnnotation( time_t time );
|
||||
RimTimeAxisAnnotation* addTimeRangeAnnotation( time_t startTime, time_t endTime );
|
||||
void removeTimeAnnotation( RimTimeAxisAnnotation* annotation );
|
||||
void removeAllTimeAnnotations();
|
||||
|
||||
void updateAxes() override;
|
||||
|
||||
|
@ -22,8 +22,11 @@
|
||||
#include "RiaTimeTTools.h"
|
||||
|
||||
#include "RimSummaryPlot.h"
|
||||
#include "RimTimeAxisAnnotation.h"
|
||||
|
||||
#include "cafPdmUiDateEditor.h"
|
||||
#include "cafPdmUiLineEditor.h"
|
||||
#include "cafPdmUiSliderEditor.h"
|
||||
#include "cafPdmUiTextEditor.h"
|
||||
|
||||
#include "ExponentialRegression.hpp"
|
||||
@ -78,6 +81,14 @@ RimSummaryRegressionAnalysisCurve::RimSummaryRegressionAnalysisCurve()
|
||||
CAF_PDM_InitFieldNoDefault( &m_forecastUnit, "ForecastUnit", "Unit" );
|
||||
CAF_PDM_InitField( &m_polynominalDegree, "PolynominalDegree", 3, "Degree" );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_minTimeStep, "MinTimeStep", "From" );
|
||||
m_minTimeStep.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_maxTimeStep, "MaxTimeStep", "To" );
|
||||
m_maxTimeStep.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitField( &m_showTimeSelectionInPlot, "ShowTimeSelectionInPlot", false, "Show In Plot" );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_expressionText, "ExpressionText", "Expression" );
|
||||
m_expressionText.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
|
||||
m_expressionText.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN );
|
||||
@ -90,6 +101,8 @@ RimSummaryRegressionAnalysisCurve::RimSummaryRegressionAnalysisCurve()
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimSummaryRegressionAnalysisCurve::~RimSummaryRegressionAnalysisCurve()
|
||||
{
|
||||
auto plot = firstAncestorOrThisOfType<RimSummaryPlot>();
|
||||
if ( plot && m_timeRangeAnnotation ) plot->removeTimeAnnotation( m_timeRangeAnnotation );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -148,29 +161,38 @@ std::tuple<std::vector<time_t>, std::vector<double>, QString>
|
||||
{
|
||||
if ( values.empty() || timeSteps.empty() ) return { timeSteps, values, "" };
|
||||
|
||||
std::vector<double> timeStepsD = convertToDouble( timeSteps );
|
||||
auto [timeStepsInRange, valuesInRange] = getInRangeValues( timeSteps, values, m_minTimeStep, m_maxTimeStep );
|
||||
|
||||
std::vector<time_t> outputTimeSteps = getOutputTimeSteps( timeSteps, m_forecastBackward(), m_forecastForward(), m_forecastUnit() );
|
||||
if ( timeStepsInRange.empty() || valuesInRange.empty() ) return {};
|
||||
|
||||
std::vector<double> timeStepsD = convertToDouble( timeStepsInRange );
|
||||
|
||||
std::vector<time_t> outputTimeSteps = getOutputTimeSteps( timeStepsInRange, m_forecastBackward(), m_forecastForward(), m_forecastUnit() );
|
||||
|
||||
std::vector<double> outputTimeStepsD = convertToDouble( outputTimeSteps );
|
||||
|
||||
if ( timeStepsD.empty() || valuesInRange.empty() )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if ( m_regressionType == RegressionType::LINEAR )
|
||||
{
|
||||
regression::LinearRegression linearRegression;
|
||||
linearRegression.fit( timeStepsD, values );
|
||||
linearRegression.fit( timeStepsD, valuesInRange );
|
||||
std::vector<double> predictedValues = linearRegression.predict( outputTimeStepsD );
|
||||
return { outputTimeSteps, predictedValues, generateRegressionText( linearRegression ) };
|
||||
}
|
||||
else if ( m_regressionType == RegressionType::POLYNOMINAL )
|
||||
{
|
||||
regression::PolynominalRegression polynominalRegression;
|
||||
polynominalRegression.fit( timeStepsD, values, m_polynominalDegree );
|
||||
polynominalRegression.fit( timeStepsD, valuesInRange, m_polynominalDegree );
|
||||
std::vector<double> predictedValues = polynominalRegression.predict( outputTimeStepsD );
|
||||
return { outputTimeSteps, predictedValues, generateRegressionText( polynominalRegression ) };
|
||||
}
|
||||
else if ( m_regressionType == RegressionType::POWER_FIT )
|
||||
{
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, values );
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, valuesInRange );
|
||||
regression::PowerFitRegression powerFitRegression;
|
||||
powerFitRegression.fit( filteredTimeSteps, filteredValues );
|
||||
std::vector<double> predictedValues = powerFitRegression.predict( outputTimeStepsD );
|
||||
@ -178,7 +200,7 @@ std::tuple<std::vector<time_t>, std::vector<double>, QString>
|
||||
}
|
||||
else if ( m_regressionType == RegressionType::EXPONENTIAL )
|
||||
{
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, values );
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, valuesInRange );
|
||||
regression::ExponentialRegression exponentialRegression;
|
||||
exponentialRegression.fit( filteredTimeSteps, filteredValues );
|
||||
std::vector<double> predictedValues = exponentialRegression.predict( outputTimeStepsD );
|
||||
@ -186,7 +208,7 @@ std::tuple<std::vector<time_t>, std::vector<double>, QString>
|
||||
}
|
||||
else if ( m_regressionType == RegressionType::LOGARITHMIC )
|
||||
{
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, values );
|
||||
auto [filteredTimeSteps, filteredValues] = getPositiveValues( timeStepsD, valuesInRange );
|
||||
regression::LogarithmicRegression logarithmicRegression;
|
||||
logarithmicRegression.fit( filteredTimeSteps, filteredValues );
|
||||
std::vector<double> predictedValues = logarithmicRegression.predict( outputTimeStepsD );
|
||||
@ -195,7 +217,7 @@ std::tuple<std::vector<time_t>, std::vector<double>, QString>
|
||||
else if ( m_regressionType == RegressionType::LOGISTIC )
|
||||
{
|
||||
regression::LogisticRegression logisticRegression;
|
||||
logisticRegression.fit( timeStepsD, values );
|
||||
logisticRegression.fit( timeStepsD, valuesInRange );
|
||||
std::vector<double> predictedValues = logisticRegression.predict( outputTimeStepsD );
|
||||
return { convertToTimeT( outputTimeStepsD ), predictedValues, generateRegressionText( logisticRegression ) };
|
||||
}
|
||||
@ -220,6 +242,11 @@ void RimSummaryRegressionAnalysisCurve::defineUiOrdering( QString uiConfigName,
|
||||
|
||||
regressionCurveGroup->add( &m_expressionText );
|
||||
|
||||
caf::PdmUiGroup* timeSelectionGroup = uiOrdering.addNewGroup( "Time Selection" );
|
||||
timeSelectionGroup->add( &m_minTimeStep );
|
||||
timeSelectionGroup->add( &m_maxTimeStep );
|
||||
timeSelectionGroup->add( &m_showTimeSelectionInPlot );
|
||||
|
||||
caf::PdmUiGroup* forecastingGroup = uiOrdering.addNewGroup( "Forecasting" );
|
||||
forecastingGroup->add( &m_forecastForward );
|
||||
forecastingGroup->add( &m_forecastBackward );
|
||||
@ -235,11 +262,23 @@ void RimSummaryRegressionAnalysisCurve::fieldChangedByUi( const caf::PdmFieldHan
|
||||
const QVariant& oldValue,
|
||||
const QVariant& newValue )
|
||||
{
|
||||
if ( &m_minTimeStep == changedField && m_minTimeStep > m_maxTimeStep )
|
||||
{
|
||||
m_maxTimeStep = m_minTimeStep;
|
||||
}
|
||||
|
||||
if ( &m_maxTimeStep == changedField && m_maxTimeStep < m_minTimeStep )
|
||||
{
|
||||
m_minTimeStep = m_maxTimeStep;
|
||||
}
|
||||
|
||||
RimSummaryCurve::fieldChangedByUi( changedField, oldValue, newValue );
|
||||
if ( changedField == &m_regressionType || changedField == &m_polynominalDegree || changedField == &m_forecastBackward ||
|
||||
changedField == &m_forecastForward || changedField == &m_forecastUnit )
|
||||
changedField == &m_forecastForward || changedField == &m_forecastUnit || changedField == &m_minTimeStep ||
|
||||
changedField == &m_maxTimeStep || changedField == &m_showTimeSelectionInPlot )
|
||||
{
|
||||
loadAndUpdateDataAndPlot();
|
||||
|
||||
auto plot = firstAncestorOrThisOfTypeAsserted<RimSummaryPlot>();
|
||||
if ( plot ) plot->zoomAll();
|
||||
}
|
||||
@ -270,6 +309,19 @@ void RimSummaryRegressionAnalysisCurve::defineEditorAttribute( const caf::PdmFie
|
||||
lineEditorAttr->validator = new QIntValidator( 0, 50, nullptr );
|
||||
}
|
||||
}
|
||||
else if ( field == &m_minTimeStep || field == &m_maxTimeStep )
|
||||
{
|
||||
if ( auto* myAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute ) )
|
||||
{
|
||||
auto timeSteps = RimSummaryCurve::timeStepsY();
|
||||
if ( !timeSteps.empty() )
|
||||
{
|
||||
myAttr->m_minimum = *timeSteps.begin();
|
||||
myAttr->m_maximum = *timeSteps.rbegin();
|
||||
}
|
||||
myAttr->m_showSpinBox = false;
|
||||
}
|
||||
}
|
||||
else if ( field == &m_expressionText )
|
||||
{
|
||||
auto myAttr = dynamic_cast<caf::PdmUiTextEditorAttribute*>( attribute );
|
||||
@ -480,3 +532,55 @@ std::pair<std::vector<double>, std::vector<double>>
|
||||
|
||||
return std::make_pair( filteredTimeSteps, filteredValues );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::pair<std::vector<time_t>, std::vector<double>> RimSummaryRegressionAnalysisCurve::getInRangeValues( const std::vector<time_t>& timeSteps,
|
||||
const std::vector<double>& values,
|
||||
time_t minTimeStep,
|
||||
time_t maxTimeStep )
|
||||
{
|
||||
CAF_ASSERT( timeSteps.size() == values.size() );
|
||||
|
||||
std::vector<time_t> filteredTimeSteps;
|
||||
std::vector<double> filteredValues;
|
||||
for ( size_t i = 0; i < timeSteps.size(); i++ )
|
||||
{
|
||||
time_t timeStep = timeSteps[i];
|
||||
if ( timeStep >= minTimeStep && timeStep <= maxTimeStep )
|
||||
{
|
||||
filteredTimeSteps.push_back( timeStep );
|
||||
filteredValues.push_back( values[i] );
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair( filteredTimeSteps, filteredValues );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryRegressionAnalysisCurve::updateTimeAnnotations()
|
||||
{
|
||||
auto plot = firstAncestorOrThisOfTypeAsserted<RimSummaryPlot>();
|
||||
if ( m_timeRangeAnnotation ) plot->removeTimeAnnotation( m_timeRangeAnnotation );
|
||||
|
||||
if ( m_showTimeSelectionInPlot )
|
||||
{
|
||||
m_timeRangeAnnotation = plot->addTimeRangeAnnotation( m_minTimeStep, m_maxTimeStep );
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryRegressionAnalysisCurve::updateDefaultValues()
|
||||
{
|
||||
auto timeSteps = RimSummaryCurve::timeStepsY();
|
||||
if ( !timeSteps.empty() )
|
||||
{
|
||||
m_minTimeStep = timeSteps.front();
|
||||
m_maxTimeStep = timeSteps.back();
|
||||
}
|
||||
}
|
||||
|
@ -18,14 +18,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafAppEnum.h"
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmObject.h"
|
||||
|
||||
#include "RimSummaryCurve.h"
|
||||
|
||||
#include "cafAppEnum.h"
|
||||
#include "regression-analysis/src/PowerFitRegression.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace regression
|
||||
@ -38,6 +36,8 @@ class PolynominalRegression;
|
||||
class PowerFitRegression;
|
||||
} // namespace regression
|
||||
|
||||
class RimTimeAxisAnnotation;
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
///
|
||||
@ -77,6 +77,11 @@ public:
|
||||
static std::vector<time_t>
|
||||
getOutputTimeSteps( const std::vector<time_t>& timeSteps, int forecastBackward, int forecastForward, ForecastUnit forecastUnit );
|
||||
|
||||
void updateDefaultValues();
|
||||
|
||||
protected:
|
||||
void updateTimeAnnotations() override;
|
||||
|
||||
private:
|
||||
void onLoadDataAndUpdate( bool updateParentPlot ) override;
|
||||
|
||||
@ -97,6 +102,9 @@ private:
|
||||
static std::pair<std::vector<double>, std::vector<double>> getPositiveValues( const std::vector<double>& timeSteps,
|
||||
const std::vector<double>& values );
|
||||
|
||||
static std::pair<std::vector<time_t>, std::vector<double>>
|
||||
getInRangeValues( const std::vector<time_t>& timeSteps, const std::vector<double>& values, time_t minTimeStep, time_t maxTimeStep );
|
||||
|
||||
static QString generateRegressionText( const regression::LinearRegression& reg );
|
||||
static QString generateRegressionText( const regression::PolynominalRegression& reg );
|
||||
static QString generateRegressionText( const regression::PowerFitRegression& reg );
|
||||
@ -109,14 +117,19 @@ private:
|
||||
static void appendTimeSteps( std::vector<time_t>& destinationTimeSteps, const std::set<QDateTime>& sourceTimeSteps );
|
||||
|
||||
caf::PdmField<caf::AppEnum<RegressionType>> m_regressionType;
|
||||
caf::PdmField<int> m_polynominalDegree;
|
||||
caf::PdmField<QString> m_expressionText;
|
||||
caf::PdmField<int> m_forecastForward;
|
||||
caf::PdmField<int> m_forecastBackward;
|
||||
caf::PdmField<caf::AppEnum<ForecastUnit>> m_forecastUnit;
|
||||
caf::PdmField<time_t> m_minTimeStep;
|
||||
caf::PdmField<time_t> m_maxTimeStep;
|
||||
caf::PdmField<bool> m_showTimeSelectionInPlot;
|
||||
|
||||
std::vector<double> m_valuesX;
|
||||
std::vector<time_t> m_timeStepsX;
|
||||
std::vector<double> m_valuesY;
|
||||
std::vector<time_t> m_timeStepsY;
|
||||
caf::PdmField<int> m_polynominalDegree;
|
||||
caf::PdmField<QString> m_expressionText;
|
||||
caf::PdmField<int> m_forecastForward;
|
||||
caf::PdmField<int> m_forecastBackward;
|
||||
caf::PdmField<caf::AppEnum<ForecastUnit>> m_forecastUnit;
|
||||
|
||||
caf::PdmPointer<RimTimeAxisAnnotation> m_timeRangeAnnotation;
|
||||
std::vector<double> m_valuesX;
|
||||
std::vector<time_t> m_timeStepsX;
|
||||
std::vector<double> m_valuesY;
|
||||
std::vector<time_t> m_timeStepsY;
|
||||
};
|
||||
|
@ -150,6 +150,7 @@ RimSummaryTimeAxisProperties::RimSummaryTimeAxisProperties()
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_annotations, "Annotations", "" );
|
||||
m_annotations.uiCapability()->setUiTreeHidden( true );
|
||||
m_annotations.uiCapability()->setUiTreeChildrenHidden( true );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@ -758,6 +759,14 @@ void RimSummaryTimeAxisProperties::removeAllAnnotations()
|
||||
m_annotations.deleteChildren();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryTimeAxisProperties::removeAnnotation( RimTimeAxisAnnotation* annotation )
|
||||
{
|
||||
m_annotations.removeChild( annotation );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
@ -101,7 +101,9 @@ public:
|
||||
|
||||
std::vector<RimPlotAxisAnnotation*> annotations() const override;
|
||||
void appendAnnotation( RimPlotAxisAnnotation* annotation ) override;
|
||||
void removeAllAnnotations() override;
|
||||
void removeAnnotation( RimTimeAxisAnnotation* annotation );
|
||||
|
||||
void removeAllAnnotations() override;
|
||||
|
||||
const QString& dateFormat() const;
|
||||
const QString& timeFormat() const;
|
||||
|
Loading…
Reference in New Issue
Block a user