QueryTool now uses the same DataGrid as the CRUD tool.
This commit is contained in:
parent
bbe52c9db8
commit
5e7fb68c55
15 changed files with 135 additions and 203 deletions
|
|
@ -30,9 +30,9 @@ set(PGLAB_SOURCES
|
|||
codeeditor/GutterPainter.cpp
|
||||
crud/CrudModel.cpp
|
||||
crud/CrudTab.cpp
|
||||
crud/DataGridWidget.cpp
|
||||
querytool/QueryExplainModel.cpp
|
||||
querytool/QueryParamListController.cpp
|
||||
querytool/QueryResultModel.cpp
|
||||
querytool/QueryTool.cpp
|
||||
serverinspector/DatabasesPage.cpp
|
||||
serverinspector/RolesPage.cpp
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "CustomDataRole.h"
|
||||
#include <QtConcurrent>
|
||||
#include <QFuture>
|
||||
#include <QBrush>
|
||||
#include <QMessageBox>
|
||||
#include "Pgsql_oids.h"
|
||||
#include "Pgsql_PgException.h"
|
||||
|
|
@ -217,6 +218,8 @@ QVariant CrudModel::data(const QModelIndex &index, int role) const
|
|||
if (value)
|
||||
return StringToVariant(value->c_str(), typ);
|
||||
}
|
||||
else if (role == Qt::ForegroundRole && !m_wholeTable && columnIsReadOnly(index.column()))
|
||||
return QBrush(Qt::gray);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,19 @@
|
|||
#include "CrudModel.h"
|
||||
#include "CustomDataRole.h"
|
||||
#include "ResultTableModelUtil.h"
|
||||
#include "util/PgLabItemDelegate.h"
|
||||
#include "DataGridWidget.h"
|
||||
#include "IntegerRange.h"
|
||||
#include "OpenDatabase.h"
|
||||
#include "catalog/PgClassContainer.h"
|
||||
#include "catalog/PgDatabaseCatalog.h"
|
||||
#include <QDebug>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QMessageBox>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <QTableView>
|
||||
|
||||
|
||||
CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent)
|
||||
|
|
@ -24,25 +27,18 @@ CrudTab::CrudTab(IDatabaseWindow *context, QWidget *parent)
|
|||
|
||||
m_db = context->openDatabase();
|
||||
|
||||
SetTableViewDefault(ui->tableView);
|
||||
|
||||
auto delegate = new PgLabItemDelegate(ui->tableView);
|
||||
ui->tableView->setItemDelegate(delegate);
|
||||
|
||||
m_crudModel = new CrudModel(nullptr);
|
||||
|
||||
m_SortFilterProxy = new QSortFilterProxyModel(this);
|
||||
m_SortFilterProxy->setSourceModel(m_crudModel);
|
||||
m_SortFilterProxy->setSortRole(CustomSortRole);
|
||||
ui->tableView->setModel(m_SortFilterProxy);
|
||||
ui->tableView->setSortingEnabled(true);
|
||||
m_SortFilterProxy->sort(0, Qt::AscendingOrder);
|
||||
m_grid = new DataGridWidget(this);
|
||||
ui->verticalLayout->addWidget(m_grid);
|
||||
m_grid->setSourceModel(m_crudModel);
|
||||
m_grid->view()->verticalHeader()->setVisible(false);
|
||||
|
||||
ui->tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
ui->tableView->addAction(ui->actionRemove_rows);
|
||||
m_grid->view()->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_grid->view()->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_grid->view()->addAction(ui->actionRemove_rows);
|
||||
|
||||
auto horizontal_header = ui->tableView->horizontalHeader();
|
||||
auto horizontal_header = m_grid->view()->horizontalHeader();
|
||||
horizontal_header->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||
connect(horizontal_header, &QHeaderView::customContextMenuRequested,
|
||||
this, &CrudTab::headerCustomContextMenu);
|
||||
|
|
@ -70,7 +66,7 @@ void CrudTab::refresh()
|
|||
void CrudTab::on_actionRemove_rows_triggered()
|
||||
{
|
||||
std::set<IntegerRange<int>> row_ranges;
|
||||
auto selection = m_SortFilterProxy->mapSelectionToSource(ui->tableView->selectionModel()->selection());
|
||||
auto selection = m_grid->proxy()->mapSelectionToSource(m_grid->view()->selectionModel()->selection());
|
||||
for (auto& range : selection) {
|
||||
row_ranges.emplace(range.top(), range.height());
|
||||
}
|
||||
|
|
@ -110,7 +106,7 @@ void CrudTab::headerCustomContextMenu(const QPoint &pos)
|
|||
|
||||
if (headerContextMenu)
|
||||
{
|
||||
auto horizontal_header = ui->tableView->horizontalHeader();
|
||||
auto horizontal_header = m_grid->view()->horizontalHeader();
|
||||
headerContextMenu->popup(horizontal_header->mapToGlobal(pos));
|
||||
}
|
||||
}
|
||||
|
|
@ -122,9 +118,9 @@ void CrudTab::gotoColumn(QString column)
|
|||
auto name = m_crudModel->headerData(i, Qt::Horizontal).toString();
|
||||
if (name == column)
|
||||
{
|
||||
auto index = ui->tableView->currentIndex();
|
||||
index = ui->tableView->model()->index(index.row(), i);
|
||||
ui->tableView->setCurrentIndex(index);
|
||||
auto index = m_grid->view()->currentIndex();
|
||||
index = m_grid->view()->model()->index(index.row(), i);
|
||||
m_grid->view()->setCurrentIndex(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
#ifndef CRUDTAB_H
|
||||
#define CRUDTAB_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include "catalog/PgClass.h"
|
||||
#include "IDatabaseWindow.h"
|
||||
#include <QWidget>
|
||||
|
|
@ -14,6 +13,7 @@ namespace Ui {
|
|||
|
||||
class OpenDatabase;
|
||||
class CrudModel;
|
||||
class DataGridWidget;
|
||||
class QMenu;
|
||||
|
||||
class CrudTab : public QWidget {
|
||||
|
|
@ -33,7 +33,7 @@ private:
|
|||
std::optional<PgClass> m_table;
|
||||
|
||||
CrudModel *m_crudModel = nullptr;
|
||||
QSortFilterProxyModel *m_SortFilterProxy = nullptr;
|
||||
DataGridWidget *m_grid = nullptr;
|
||||
|
||||
QMenu *headerContextMenu = nullptr;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,15 +13,7 @@
|
|||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QVBoxLayout" name="verticalLayout"/>
|
||||
<action name="actionRemove_rows">
|
||||
<property name="text">
|
||||
<string>Remove rows</string>
|
||||
|
|
|
|||
34
pglab/crud/DataGridWidget.cpp
Normal file
34
pglab/crud/DataGridWidget.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#include "DataGridWidget.h"
|
||||
#include "CustomDataRole.h"
|
||||
#include "ResultTableModelUtil.h"
|
||||
#include "util/PgLabItemDelegate.h"
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTableView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
|
||||
DataGridWidget::DataGridWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_view = new QTableView(this);
|
||||
layout->addWidget(m_view);
|
||||
|
||||
SetTableViewDefault(m_view);
|
||||
auto delegate = new PgLabItemDelegate(m_view);
|
||||
m_view->setItemDelegate(delegate);
|
||||
}
|
||||
|
||||
void DataGridWidget::setSourceModel(QAbstractItemModel *model)
|
||||
{
|
||||
if (!m_proxy) {
|
||||
m_proxy = new QSortFilterProxyModel(this);
|
||||
m_proxy->setSortRole(CustomSortRole);
|
||||
}
|
||||
m_proxy->setSourceModel(model);
|
||||
m_view->setModel(m_proxy);
|
||||
m_view->setSortingEnabled(true);
|
||||
m_proxy->sort(0, Qt::AscendingOrder);
|
||||
}
|
||||
28
pglab/crud/DataGridWidget.h
Normal file
28
pglab/crud/DataGridWidget.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef DATAGRIDWIDGET_H
|
||||
#define DATAGRIDWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QSortFilterProxyModel;
|
||||
class QTableView;
|
||||
|
||||
/** Sortable and editable grid shared by the CRUD tab and the query result tab.
|
||||
*/
|
||||
class DataGridWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DataGridWidget(QWidget *parent = nullptr);
|
||||
|
||||
/// Sets the model to show, sorting is done by a proxy that uses CustomSortRole
|
||||
void setSourceModel(QAbstractItemModel *model);
|
||||
|
||||
QTableView *view() const { return m_view; }
|
||||
QSortFilterProxyModel *proxy() const { return m_proxy; }
|
||||
|
||||
private:
|
||||
QTableView *m_view = nullptr;
|
||||
QSortFilterProxyModel *m_proxy = nullptr;
|
||||
};
|
||||
|
||||
#endif // DATAGRIDWIDGET_H
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
#include "QueryResultModel.h"
|
||||
#include "ResultTableModelUtil.h"
|
||||
#include "Pgsql_declare.h"
|
||||
#include "Pgsql_oids.h"
|
||||
#include "catalog/PgDatabaseCatalog.h"
|
||||
#include "CustomDataRole.h"
|
||||
#include <QBrush>
|
||||
#include <QColor>
|
||||
|
||||
using namespace Pgsql;
|
||||
|
||||
QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r
|
||||
, std::shared_ptr<PgDatabaseCatalog> cat)
|
||||
: QAbstractTableModel(parent)
|
||||
, result(std::move(r))
|
||||
, catalog(cat)
|
||||
{}
|
||||
|
||||
|
||||
int QueryResultModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
int r = result->rows();
|
||||
return r;
|
||||
}
|
||||
|
||||
int QueryResultModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
int r = result->cols();
|
||||
return r;
|
||||
}
|
||||
|
||||
QVariant QueryResultModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
// static const QString null_str("null");
|
||||
QVariant v;
|
||||
|
||||
const int col = index.column();
|
||||
const int rij = index.row();
|
||||
const Oid oid = result->type(col);
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
if (result->null(col, rij)) {
|
||||
//v = nullptr;
|
||||
}
|
||||
else {
|
||||
Value value = result->get(col, rij);
|
||||
if (oid == bool_oid) {
|
||||
bool b = value;
|
||||
v = b;
|
||||
}
|
||||
else {
|
||||
QString s = value;
|
||||
v = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (role == CustomDataTypeRole) {
|
||||
v = oid;
|
||||
}
|
||||
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QVariant QueryResultModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
QVariant r;
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (orientation == Qt::Horizontal) {
|
||||
QString s(result->getColName(section));
|
||||
s += "\n";
|
||||
s += getTypeDisplayString(*catalog, result->type(section));
|
||||
r = s;
|
||||
}
|
||||
else {
|
||||
r = QString::number(section + 1);
|
||||
}
|
||||
}
|
||||
Qt::ItemFlags f;
|
||||
return r;
|
||||
}
|
||||
|
||||
Qt::ItemFlags QueryResultModel::flags(const QModelIndex &) const
|
||||
{
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#ifndef QUERYRESULTMODEL_H
|
||||
#define QUERYRESULTMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include "catalog/models/BaseTableModel.h"
|
||||
#include "Pgsql_Connection.h"
|
||||
|
||||
class PgDatabaseCatalog;
|
||||
|
||||
class QueryResultModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QueryResultModel(QObject *parent, std::shared_ptr<Pgsql::Result> r, std::shared_ptr<PgDatabaseCatalog> cat);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return result; }
|
||||
protected:
|
||||
// virtual Oid getType(int column) const override;
|
||||
// virtual QVariant getData(const QModelIndex &index) const override;
|
||||
private:
|
||||
std::shared_ptr<Pgsql::Result> result;
|
||||
std::shared_ptr<PgDatabaseCatalog> catalog;
|
||||
};
|
||||
|
||||
#endif // QUERYRESULTMODEL_H
|
||||
|
|
@ -476,10 +476,8 @@ void QueryTool::query_ready(std::shared_ptr<Pgsql::Result> dbres, qint64 elapsed
|
|||
//int n_rows = dbres->getRows();
|
||||
//QString rowcount_str = QString("rows: %1").arg(dbres->getRows());
|
||||
|
||||
auto result_model = std::make_shared<QueryResultModel>(nullptr , dbres,
|
||||
m_catalog);
|
||||
TuplesResultWidget *trw = new TuplesResultWidget;
|
||||
trw->setResult(result_model, elapsedms);
|
||||
trw->setResult(m_context->openDatabase(), dbres, elapsedms);
|
||||
resultList.push_back(trw);
|
||||
ui->tabWidget->addTab(trw, "Data");
|
||||
if (resultList.size() == 1)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "ManagedPage.h"
|
||||
#include "ASyncDBConnection.h"
|
||||
#include "QueryResultModel.h"
|
||||
#include "QueryExplainModel.h"
|
||||
#include "stopwatch.h"
|
||||
#include "tuplesresultwidget.h"
|
||||
|
|
@ -26,7 +25,6 @@ class QTableView;
|
|||
class QTabWidget;
|
||||
class SqlSyntaxHighlighter;
|
||||
class ExplainRoot;
|
||||
class QueryResultModel;
|
||||
class QueryExplainModel;
|
||||
class PgTypeContainer;
|
||||
class OpenDatabase;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
#include "tuplesresultwidget.h"
|
||||
#include "tuplesresultwidget.h"
|
||||
#include "ui_tuplesresultwidget.h"
|
||||
#include "ResultTableModelUtil.h"
|
||||
#include "util.h"
|
||||
#include "crud/CrudModel.h"
|
||||
#include "crud/DataGridWidget.h"
|
||||
#include "OpenDatabase.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include "util/PgLabItemDelegate.h"
|
||||
#include <QTableView>
|
||||
#include <QHeaderView>
|
||||
|
||||
|
||||
TuplesResultWidget::TuplesResultWidget(QWidget *parent) :
|
||||
|
|
@ -15,9 +19,13 @@ TuplesResultWidget::TuplesResultWidget(QWidget *parent) :
|
|||
ui->setupUi(this);
|
||||
ui->lblRowCount->setText(QString());
|
||||
|
||||
SetTableViewDefault(ui->ResultView);
|
||||
auto delegate = new PgLabItemDelegate(ui->ResultView);
|
||||
ui->ResultView->setItemDelegate(delegate);
|
||||
m_grid = new DataGridWidget(this);
|
||||
ui->verticalLayout->insertWidget(0, m_grid);
|
||||
m_grid->view()->setFont(QFont("Source Sans Pro", 10));
|
||||
m_grid->view()->setAlternatingRowColors(true);
|
||||
m_grid->view()->setWordWrap(false);
|
||||
m_grid->view()->verticalHeader()->setDefaultSectionSize(21);
|
||||
m_grid->view()->verticalHeader()->setMinimumSectionSize(16);
|
||||
}
|
||||
|
||||
TuplesResultWidget::~TuplesResultWidget()
|
||||
|
|
@ -25,14 +33,17 @@ TuplesResultWidget::~TuplesResultWidget()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void TuplesResultWidget::setResult(std::shared_ptr<QueryResultModel> res, float ms)
|
||||
void TuplesResultWidget::setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> res, float ms)
|
||||
{
|
||||
resultModel = res;
|
||||
ui->ResultView->setModel(resultModel.get());
|
||||
m_result = res;
|
||||
|
||||
ui->ResultView->resizeColumnsToContents();
|
||||
m_model = new CrudModel(m_grid);
|
||||
m_model->setResult(db, res);
|
||||
m_grid->setSourceModel(m_model);
|
||||
|
||||
QString rowcount_str = QString("rows: %1").arg(resultModel->rowCount());
|
||||
m_grid->view()->resizeColumnsToContents();
|
||||
|
||||
QString rowcount_str = QString("rows: %1").arg(m_model->rowCount());
|
||||
ui->lblRowCount->setText(rowcount_str);
|
||||
ui->lblElapsedTime->setText(msfloatToHumanReadableString(ms));
|
||||
}
|
||||
|
|
@ -42,8 +53,6 @@ void TuplesResultWidget::exportData(const QString &file_name) const
|
|||
QFile file(file_name);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
QTextStream out(&file);
|
||||
::exportTable(ui->ResultView, out);
|
||||
::exportTable(m_grid->view(), out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
#ifndef TUPLESRESULTWIDGET_H
|
||||
#define TUPLESRESULTWIDGET_H
|
||||
|
||||
#include "querytool/QueryResultModel.h"
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
|
||||
namespace Ui {
|
||||
class TuplesResultWidget;
|
||||
}
|
||||
|
||||
namespace Pgsql {
|
||||
class Result;
|
||||
}
|
||||
|
||||
class OpenDatabase;
|
||||
class CrudModel;
|
||||
class DataGridWidget;
|
||||
|
||||
class TuplesResultWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
@ -16,14 +24,15 @@ public:
|
|||
explicit TuplesResultWidget(QWidget *parent = 0);
|
||||
~TuplesResultWidget();
|
||||
|
||||
void setResult(std::shared_ptr<QueryResultModel> res, float ms);
|
||||
void setResult(std::shared_ptr<OpenDatabase> db, std::shared_ptr<Pgsql::Result> res, float ms);
|
||||
void exportData(const QString &file_name) const;
|
||||
|
||||
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return resultModel->GetPgsqlResult(); }
|
||||
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return m_result; }
|
||||
private:
|
||||
Ui::TuplesResultWidget *ui;
|
||||
|
||||
std::shared_ptr<QueryResultModel> resultModel;
|
||||
DataGridWidget *m_grid = nullptr;
|
||||
CrudModel *m_model = nullptr;
|
||||
std::shared_ptr<Pgsql::Result> m_result;
|
||||
};
|
||||
|
||||
#endif // TUPLESRESULTWIDGET_H
|
||||
|
|
|
|||
|
|
@ -29,37 +29,6 @@
|
|||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="ResultView">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Source Sans Pro</family>
|
||||
<pointsize>10</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>21</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderMinimumSectionSize">
|
||||
<number>16</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Query results are now shown in the same grid as the table data tab. Columns
|
||||
can be sorted by clicking their headers, and cells that belong to a table
|
||||
with a primary key present in the result can be edited directly. Changes
|
||||
are saved with an ``UPDATE`` by primary key.
|
||||
other:
|
||||
- |
|
||||
Read-only cells in query results, such as expression columns or columns of
|
||||
tables without a primary key in the result, are now shown in grey so they
|
||||
can be told apart from editable cells.
|
||||
Loading…
Add table
Add a link
Reference in a new issue