diff --git a/web/config.py b/web/config.py
index e08eda8d..d314d834 100644
--- a/web/config.py
+++ b/web/config.py
@@ -245,6 +245,9 @@ SQLITE_TIMEOUT = 500
# Set to False to disable password saving.
ALLOW_SAVE_PASSWORD = True
+# Maximum count of history queries stored per user/server/database
+MAX_QUERY_HIST_STORED = 20
+
##########################################################################
# Server-side session storage path
#
diff --git a/web/migrations/versions/ec1cac3399c9_.py b/web/migrations/versions/ec1cac3399c9_.py
new file mode 100644
index 00000000..e3416851
--- /dev/null
+++ b/web/migrations/versions/ec1cac3399c9_.py
@@ -0,0 +1,42 @@
+
+"""empty message
+
+Revision ID: ec1cac3399c9
+Revises: b5b87fdfcb30
+Create Date: 2019-03-07 16:05:28.874203
+
+"""
+from pgadmin.model import db
+
+
+# revision identifiers, used by Alembic.
+revision = 'ec1cac3399c9'
+down_revision = 'b5b87fdfcb30'
+branch_labels = None
+depends_on = None
+
+srno = db.Column(db.Integer(), nullable=False, primary_key=True)
+uid = db.Column(
+ db.Integer, db.ForeignKey('user.id'), nullable=False, primary_key=True
+)
+sid = db.Column(db.Integer(), nullable=False, primary_key=True)
+did = db.Column(db.Integer(), nullable=False, primary_key=True)
+query = db.Column(db.String(), nullable=False)
+
+def upgrade():
+ db.engine.execute("""
+ CREATE TABLE query_history (
+ srno INTEGER NOT NULL,
+ uid INTEGER NOT NULL,
+ sid INTEGER NOT NULL,
+ did INTEGER NOT NULL,
+ query_info TEXT NOT NULL,
+ last_updated_flag TEXT NOT NULL,
+ PRIMARY KEY (srno, uid, sid, did),
+ FOREIGN KEY(uid) REFERENCES user (id),
+ FOREIGN KEY(sid) REFERENCES server (id)
+ )""")
+
+
+def downgrade():
+ pass
diff --git a/web/pgadmin/model/__init__.py b/web/pgadmin/model/__init__.py
index eda922b8..14d65e94 100644
--- a/web/pgadmin/model/__init__.py
+++ b/web/pgadmin/model/__init__.py
@@ -29,7 +29,7 @@ from flask_sqlalchemy import SQLAlchemy
#
##########################################################################
-SCHEMA_VERSION = 21
+SCHEMA_VERSION = 22
##########################################################################
#
@@ -267,3 +267,18 @@ class Keys(db.Model):
__tablename__ = 'keys'
name = db.Column(db.String(), nullable=False, primary_key=True)
value = db.Column(db.String(), nullable=False)
+
+
+class QueryHistoryModel(db.Model):
+ """Define the history SQL table."""
+ __tablename__ = 'query_history'
+ srno = db.Column(db.Integer(), nullable=False, primary_key=True)
+ uid = db.Column(
+ db.Integer, db.ForeignKey('user.id'), nullable=False, primary_key=True
+ )
+ sid = db.Column(
+ db.Integer(), db.ForeignKey('server.id'), nullable=False,
+ primary_key=True)
+ did = db.Column(db.Integer(), nullable=False, primary_key=True)
+ query_info = db.Column(db.String(), nullable=False)
+ last_updated_flag = db.Column(db.String(), nullable=False)
diff --git a/web/pgadmin/static/js/sqleditor/history/query_history.js b/web/pgadmin/static/js/sqleditor/history/query_history.js
index b9668f9f..7c429d52 100644
--- a/web/pgadmin/static/js/sqleditor/history/query_history.js
+++ b/web/pgadmin/static/js/sqleditor/history/query_history.js
@@ -10,6 +10,7 @@ export default class QueryHistory {
this.histCollection = histModel;
this.editorPref = {};
+ this.onCopyToEditorHandler = ()=>{};
this.histCollection.onAdd(this.onAddEntry.bind(this));
this.histCollection.onReset(this.onResetEntries.bind(this));
}
@@ -35,6 +36,14 @@ export default class QueryHistory {
this.render();
}
+ onCopyToEditorClick(onCopyToEditorHandler) {
+ this.onCopyToEditorHandler = onCopyToEditorHandler;
+
+ if(this.queryHistDetails) {
+ this.queryHistDetails.onCopyToEditorClick(this.onCopyToEditorHandler);
+ }
+ }
+
setEditorPref(editorPref) {
this.editorPref = editorPref;
if(this.queryHistDetails) {
@@ -63,6 +72,7 @@ export default class QueryHistory {
this.queryHistDetails = new QueryHistoryDetails($histDetails);
this.queryHistDetails.setEditorPref(this.editorPref);
+ this.queryHistDetails.onCopyToEditorClick(this.onCopyToEditorHandler);
this.queryHistDetails.render();
this.queryHistEntries = new QueryHistoryEntries($histEntries);
diff --git a/web/pgadmin/static/js/sqleditor/history/query_history_details.js b/web/pgadmin/static/js/sqleditor/history/query_history_details.js
index 03c4a030..9124c419 100644
--- a/web/pgadmin/static/js/sqleditor/history/query_history_details.js
+++ b/web/pgadmin/static/js/sqleditor/history/query_history_details.js
@@ -10,6 +10,7 @@ export default class QueryHistoryDetails {
this.timeout = null;
this.isRendered = false;
this.sqlFontSize = null;
+ this.onCopyToEditorHandler = ()=>{};
this.editorPref = {
'sql_font_size': '1em',
@@ -47,7 +48,7 @@ export default class QueryHistoryDetails {
}
formatDate(date) {
- return moment(date).format('M-D-YY HH:mm:ss');
+ return moment(date).format('L HH:mm:ss');
}
copyAllHandler() {
@@ -62,6 +63,10 @@ export default class QueryHistoryDetails {
}, 1500);
}
+ onCopyToEditorClick(onCopyToEditorHandler) {
+ this.onCopyToEditorHandler = onCopyToEditorHandler;
+ }
+
clearPreviousTimeout() {
if (this.timeout) {
clearTimeout(this.timeout);
@@ -71,11 +76,11 @@ export default class QueryHistoryDetails {
updateCopyButton(copied) {
if (copied) {
- this.$copyBtn.attr('class', 'was-copied');
+ this.$copyBtn.addClass('was-copied').removeClass('copy-all');
this.$copyBtn.text('Copied!');
} else {
- this.$copyBtn.attr('class', 'copy-all');
- this.$copyBtn.text('Copy All');
+ this.$copyBtn.addClass('copy-all').removeClass('was-copied');
+ this.$copyBtn.text('Copy');
}
}
@@ -137,7 +142,8 @@ export default class QueryHistoryDetails {
-
+
+
@@ -154,8 +160,12 @@ export default class QueryHistoryDetails {
);
this.$errMsgBlock = this.parentNode.find('.error-message-block');
- this.$copyBtn = this.parentNode.find('#history-detail-query button');
+ this.$copyBtn = this.parentNode.find('#history-detail-query .btn-copy');
this.$copyBtn.off('click').on('click', this.copyAllHandler.bind(this));
+ this.$copyToEditor = this.parentNode.find('#history-detail-query .btn-copy-editor');
+ this.$copyToEditor.off('click').on('click', () => {
+ this.onCopyToEditorHandler(this.entry.query);
+ });
this.$metaData = this.parentNode.find('.metadata-block');
this.query_codemirror = CodeMirror(
this.parentNode.find('#history-detail-query div')[0],
diff --git a/web/pgadmin/static/js/sqleditor/history/query_history_entries.js b/web/pgadmin/static/js/sqleditor/history/query_history_entries.js
index eba2cb0b..c6fb4781 100644
--- a/web/pgadmin/static/js/sqleditor/history/query_history_entries.js
+++ b/web/pgadmin/static/js/sqleditor/history/query_history_entries.js
@@ -66,9 +66,13 @@ export class QueryHistoryItem {
return moment(date).format('HH:mm:ss');
}
+ dataKey() {
+ return this.formatDate(this.entry.start_time);
+ }
+
render() {
this.$el = $(
- `
+ `
${this.entry.query}
@@ -98,15 +102,10 @@ export class QueryHistoryEntries {
}
focus() {
- let self = this;
-
if (!this.$selectedItem) {
this.setSelectedListItem(this.$el.find('.list-item').first());
}
-
- setTimeout(() => {
- self.$selectedItem.trigger('click');
- }, 500);
+ this.$el[0].focus();
}
isArrowDown(event) {
@@ -170,7 +169,8 @@ export class QueryHistoryEntries {
}
$listItem.addClass('selected');
this.$selectedItem = $listItem;
- this.$selectedItem[0].scrollIntoView(false);
+
+ this.$selectedItem[0].scrollIntoView({block: 'center'});
if (this.onSelectedChangeHandler) {
this.onSelectedChangeHandler(this.$selectedItem.data('entrydata'));
@@ -200,13 +200,20 @@ export class QueryHistoryEntries {
entry.start_time,
entryGroupKey
).render();
- if (groups[groupIdx]) {
- $groupEl.insertBefore(groups[groupIdx]);
- } else {
- this.$el.prepend($groupEl);
+
+ let i=0;
+ while(i
groupsKeys[i]) {
+ $groupEl.insertBefore(groups[i]);
+ break;
+ }
+ i++;
+ }
+ if(i == groupsKeys.length) {
+ this.$el.append($groupEl);
}
} else if (groupIdx >= 0) {
- /* if groups present, but this is a new one */
+ /* if the group is present */
$groupEl = $(groups[groupIdx]);
}
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index e2dd5a47..4192f5c6 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -18,7 +18,7 @@ import simplejson as json
from flask import Response, url_for, render_template, session, request, \
current_app
from flask_babelex import gettext
-from flask_security import login_required
+from flask_security import login_required, current_user
from config import PG_DEFAULT_DRIVER, ON_DEMAND_RECORD_COUNT
from pgadmin.misc.file_manager import Filemanager
@@ -42,6 +42,7 @@ from pgadmin.tools.sqleditor.utils.query_tool_preferences import \
from pgadmin.tools.sqleditor.utils.query_tool_fs_utils import \
read_file_generator
from pgadmin.tools.sqleditor.utils.filter_dialog import FilterDialog
+from pgadmin.tools.sqleditor.utils.query_history import QueryHistory
MODULE_NAME = 'sqleditor'
@@ -113,7 +114,10 @@ class SqlEditorModule(PgAdminModule):
'sqleditor.query_tool_download',
'sqleditor.connection_status',
'sqleditor.get_filter_data',
- 'sqleditor.set_filter_data'
+ 'sqleditor.set_filter_data',
+ 'sqleditor.get_query_history',
+ 'sqleditor.add_query_history',
+ 'sqleditor.clear_query_history',
]
def register_preferences(self):
@@ -1504,3 +1508,54 @@ def set_filter_data(trans_id):
request=request,
trans_id=trans_id
)
+
+
+@blueprint.route(
+ '/query_history//',
+ methods=["POST"], endpoint='add_query_history'
+)
+@login_required
+def add_query_history(sid, did):
+ """
+ This method adds to query history for user/server/database
+
+ Args:
+ sid: server id
+ did: database id
+ """
+
+ return QueryHistory.save(current_user.id, sid, did, request=request)
+
+
+@blueprint.route(
+ '/query_history//',
+ methods=["DELETE"], endpoint='clear_query_history'
+)
+@login_required
+def clear_query_history(sid, did):
+ """
+ This method returns clears history for user/server/database
+
+ Args:
+ sid: server id
+ did: database id
+ """
+
+ return QueryHistory.clear(current_user.id, sid, did)
+
+
+@blueprint.route(
+ '/query_history//',
+ methods=["GET"], endpoint='get_query_history'
+)
+@login_required
+def get_query_history(sid, did):
+ """
+ This method returns query history for user/server/database
+
+ Args:
+ sid: server id
+ did: database id
+ """
+
+ return QueryHistory.get(current_user.id, sid, did)
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 7b4da193..ff1cf2dc 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -193,11 +193,11 @@ define('tools.querytool', [
});
sql_panel.load(main_docker);
- var sql_panel_obj = main_docker.addPanel('sql_panel', wcDocker.DOCK.TOP);
+ self.sql_panel_obj = main_docker.addPanel('sql_panel', wcDocker.DOCK.TOP);
var text_container = $('');
var output_container = $('').append(text_container);
- sql_panel_obj.$container.find('.pg-panel-content').append(output_container);
+ self.sql_panel_obj.$container.find('.pg-panel-content').append(output_container);
self.query_tool_obj = CodeMirror.fromTextArea(text_container.get(0), {
tabindex: '0',
@@ -222,7 +222,7 @@ define('tools.querytool', [
// Refresh Code mirror on SQL panel resize to
// display its value properly
- sql_panel_obj.on(wcDocker.EVENT.RESIZE_ENDED, function() {
+ self.sql_panel_obj.on(wcDocker.EVENT.RESIZE_ENDED, function() {
setTimeout(function() {
if (self && self.query_tool_obj) {
self.query_tool_obj.refresh();
@@ -312,8 +312,8 @@ define('tools.querytool', [
geometry_viewer.load(main_docker);
// Add all the panels to the docker
- self.scratch_panel = main_docker.addPanel('scratch', wcDocker.DOCK.RIGHT, sql_panel_obj);
- self.history_panel = main_docker.addPanel('history', wcDocker.DOCK.STACKED, sql_panel_obj);
+ self.scratch_panel = main_docker.addPanel('scratch', wcDocker.DOCK.RIGHT, self.sql_panel_obj);
+ self.history_panel = main_docker.addPanel('history', wcDocker.DOCK.STACKED, self.sql_panel_obj);
self.data_output_panel = main_docker.addPanel('data_output', wcDocker.DOCK.BOTTOM);
self.explain_panel = main_docker.addPanel('explain', wcDocker.DOCK.STACKED, self.data_output_panel);
self.messages_panel = main_docker.addPanel('messages', wcDocker.DOCK.STACKED, self.data_output_panel);
@@ -1309,12 +1309,47 @@ define('tools.querytool', [
if(!self.historyComponent) {
self.historyComponent = new QueryHistory($('#history_grid'), self.history_collection);
+
+ /* Copy query to query editor, set the focus to editor and move cursor to end */
+ self.historyComponent.onCopyToEditorClick((query)=>{
+ self.query_tool_obj.setValue(query);
+ self.sql_panel_obj.focus();
+ setTimeout(() => {
+ self.query_tool_obj.focus();
+ self.query_tool_obj.setCursor(self.query_tool_obj.lineCount(), 0);
+ }, 300);
+ });
+
self.historyComponent.render();
+
+ self.history_panel.off(wcDocker.EVENT.VISIBILITY_CHANGED);
+ self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() {
+ if (self.history_panel.isVisible()) {
+ setTimeout(()=>{
+ self.historyComponent.focus();
+ }, 500);
+ }
+ });
}
- self.history_panel.off(wcDocker.EVENT.VISIBILITY_CHANGED);
- self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() {
- self.historyComponent.focus();
+ // Make ajax call to get history data
+ $.ajax({
+ url: url_for('sqleditor.get_query_history', {
+ 'sid': self.handler.url_params.sid,
+ 'did': self.handler.url_params.did,
+ }),
+ method: 'GET',
+ contentType: 'application/json',
+ })
+ .done(function(res) {
+ res.data.result.map((entry) => {
+ let newEntry = JSON.parse(entry);
+ newEntry.start_time = new Date(newEntry.start_time);
+ self.history_collection.add(newEntry);
+ });
+ })
+ .fail(function() {
+ /* history fetch fail should not affect query tool */
});
},
@@ -1637,11 +1672,25 @@ define('tools.querytool', [
}
alertify.confirm(gettext('Clear history'),
- gettext('Are you sure you wish to clear the history?'),
+ gettext('Are you sure you wish to clear the history?') + '' +
+ gettext('This will remove all of your query history from this and other sessions for this database.'),
function() {
if (self.history_collection) {
self.history_collection.reset();
}
+
+ $.ajax({
+ url: url_for('sqleditor.clear_query_history', {
+ 'sid': self.handler.url_params.sid,
+ 'did': self.handler.url_params.did,
+ }),
+ method: 'DELETE',
+ contentType: 'application/json',
+ })
+ .done(function() {})
+ .fail(function() {
+ /* history clear fail should not affect query tool */
+ });
setTimeout(() => { self.query_tool_obj.focus(); }, 200);
},
function() {
@@ -2573,14 +2622,32 @@ define('tools.querytool', [
self.query_start_time,
new Date());
}
- self.gridView.history_collection.add({
+
+ let hist_entry = {
'status': status,
'start_time': self.query_start_time,
'query': self.query,
'row_affected': self.rows_affected,
'total_time': self.total_time,
'message': msg,
- });
+ };
+
+ /* Make ajax call to save the history data
+ * Do not bother query tool if failed to save
+ */
+ $.ajax({
+ url: url_for('sqleditor.add_query_history', {
+ 'sid': self.url_params.sid,
+ 'did': self.url_params.did,
+ }),
+ method: 'POST',
+ contentType: 'application/json',
+ data: JSON.stringify(hist_entry),
+ })
+ .done(function() {})
+ .fail(function() {});
+
+ self.gridView.history_collection.add(hist_entry);
}
},
diff --git a/web/pgadmin/tools/sqleditor/static/scss/_history.scss b/web/pgadmin/tools/sqleditor/static/scss/_history.scss
index 9751da7c..37ed8b0e 100644
--- a/web/pgadmin/tools/sqleditor/static/scss/_history.scss
+++ b/web/pgadmin/tools/sqleditor/static/scss/_history.scss
@@ -143,7 +143,7 @@
height: 0;
position: relative;
- .copy-all, .was-copied {
+ .copy-all, .was-copied, .copy-to-editor {
float: left;
position: relative;
z-index: 10;
diff --git a/web/pgadmin/tools/sqleditor/tests/test_editor_history.py b/web/pgadmin/tools/sqleditor/tests/test_editor_history.py
new file mode 100644
index 00000000..a3159ffc
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/tests/test_editor_history.py
@@ -0,0 +1,96 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2019, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+ database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+
+
+class TestEditorHistory(BaseTestGenerator):
+ """ This class will test the query tool polling. """
+ scenarios = [
+ ('When first query is hit',
+ dict(
+ entry="""{
+ query: 'first sql statement',
+ start_time: '2017-05-03T14:03:15.150Z',
+ status: true,
+ row_affected: 12345,
+ total_time: '14 msec',
+ message: 'something important ERROR: message
+ from first sql query',
+ }""",
+ clear=False,
+ expected_len=1
+ )),
+ ('When second query is hit',
+ dict(
+ entry="""{
+ query: 'second sql statement',
+ start_time: '2016-04-03T14:03:15.99Z',
+ status: true,
+ row_affected: 12345,
+ total_time: '14 msec',
+ message: 'something important ERROR: message from
+ second sql query',
+ }""",
+ clear=False,
+ expected_len=2
+ )),
+ ('When cleared',
+ dict(
+ clear=True,
+ expected_len=0
+ ))
+ ]
+
+ def setUp(self):
+ database_info = parent_node_dict["database"][-1]
+ self.server_id = database_info["server_id"]
+
+ self.db_id = database_info["db_id"]
+ db_con = database_utils.connect_database(self,
+ utils.SERVER_GROUP,
+ self.server_id,
+ self.db_id)
+ if not db_con["info"] == "Database connected.":
+ raise Exception("Could not connect to the database.")
+
+ def runTest(self):
+ url = '/sqleditor/query_history/{0}/{1}'.format(self.server_id,
+ self.db_id)
+
+ if not self.clear:
+ response = self.tester.post(url, data=self.entry)
+ self.assertEquals(response.status_code, 200)
+
+ response = self.tester.get(url)
+ self.assertEquals(response.status_code, 200)
+
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.assertEquals(len(response_data['data']['result']),
+ self.expected_len)
+ else:
+ response = self.tester.delete(url)
+ self.assertEquals(response.status_code, 200)
+
+ response = self.tester.get(url)
+ self.assertEquals(response.status_code, 200)
+
+ response_data = json.loads(response.data.decode('utf-8'))
+ self.assertEquals(len(response_data['data']['result']),
+ self.expected_len)
+
+ def tearDown(self):
+ # Disconnect the database
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/tools/sqleditor/utils/query_history.py b/web/pgadmin/tools/sqleditor/utils/query_history.py
new file mode 100644
index 00000000..da5127ce
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/utils/query_history.py
@@ -0,0 +1,111 @@
+from pgadmin.utils.ajax import make_json_response
+from pgadmin.model import db, QueryHistoryModel
+from config import MAX_QUERY_HIST_STORED
+
+
+class QueryHistory:
+ @staticmethod
+ def get(uid, sid, did):
+
+ result = db.session \
+ .query(QueryHistoryModel.query_info) \
+ .filter(QueryHistoryModel.uid == uid,
+ QueryHistoryModel.sid == sid,
+ QueryHistoryModel.did == did) \
+ .all()
+
+ return make_json_response(
+ data={
+ 'status': True,
+ 'msg': '',
+ 'result': [rec.query_info for rec in result]
+ }
+ )
+
+ @staticmethod
+ def save(uid, sid, did, request):
+ try:
+ max_srno = db.session\
+ .query(db.func.max(QueryHistoryModel.srno)) \
+ .filter(QueryHistoryModel.uid == uid,
+ QueryHistoryModel.sid == sid,
+ QueryHistoryModel.did == did)\
+ .scalar()
+
+ # if no records present
+ if max_srno is None:
+ new_srno = 1
+ else:
+ new_srno = max_srno + 1
+
+ # last updated flag is used to recognise the last
+ # inserted/updated record.
+ # It is helpful to cycle the records
+ last_updated_rec = db.session.query(QueryHistoryModel) \
+ .filter(QueryHistoryModel.uid == uid,
+ QueryHistoryModel.sid == sid,
+ QueryHistoryModel.did == did,
+ QueryHistoryModel.last_updated_flag == 'Y') \
+ .first()
+
+ # there should be a last updated record
+ # if not present start from sr no 1
+ if last_updated_rec is not None:
+ last_updated_rec.last_updated_flag = 'N'
+
+ # if max limit reached then recycle
+ if new_srno > MAX_QUERY_HIST_STORED:
+ new_srno = (
+ last_updated_rec.srno % MAX_QUERY_HIST_STORED) + 1
+ else:
+ new_srno = 1
+
+ # if the limit is lowered and number of records present is
+ # more, then cleanup
+ if max_srno > MAX_QUERY_HIST_STORED:
+ db.session.query(QueryHistoryModel)\
+ .filter(QueryHistoryModel.uid == uid,
+ QueryHistoryModel.sid == sid,
+ QueryHistoryModel.did == did,
+ QueryHistoryModel.srno >
+ MAX_QUERY_HIST_STORED)\
+ .delete()
+
+ history_entry = QueryHistoryModel(
+ srno=new_srno, uid=uid, sid=sid, did=did,
+ query_info=request.data, last_updated_flag='Y')
+
+ db.session.merge(history_entry)
+
+ db.session.commit()
+ except Exception:
+ db.session.rollback()
+ # do not affect query execution if history saving fails
+
+ return make_json_response(
+ data={
+ 'status': True,
+ 'msg': 'Success',
+ }
+ )
+
+ @staticmethod
+ def clear(uid, sid, did):
+ try:
+ db.session.query(QueryHistoryModel) \
+ .filter(QueryHistoryModel.uid == uid,
+ QueryHistoryModel.sid == sid,
+ QueryHistoryModel.did == did) \
+ .delete()
+
+ db.session.commit()
+ except Exception:
+ db.session.rollback()
+ # do not affect query execution if history clear fails
+
+ return make_json_response(
+ data={
+ 'status': True,
+ 'msg': 'Success',
+ }
+ )
diff --git a/web/regression/javascript/history/query_history_spec.js b/web/regression/javascript/history/query_history_spec.js
index 908aa2a9..4185ba3f 100644
--- a/web/regression/javascript/history/query_history_spec.js
+++ b/web/regression/javascript/history/query_history_spec.js
@@ -70,6 +70,7 @@ describe('QueryHistory', () => {
historyCollection = new HistoryCollection(historyObjects);
historyComponent = new QueryHistory(historyWrapper, historyCollection);
+ historyComponent.onCopyToEditorClick(()=>{});
historyComponent.render();
queryEntries = historyWrapper.find('#query_list .list-item');
@@ -92,8 +93,9 @@ describe('QueryHistory', () => {
expect($(queryEntries[1]).find('.timestamp').text()).toBe('01:33:05');
});
- it('renders the most recent query as selected', () => {
+ it('renders the most recent query as selected', (done) => {
expect($(queryEntries[0]).hasClass('selected')).toBeTruthy();
+ done();
});
it('renders the older query as not selected', () => {
@@ -103,10 +105,11 @@ describe('QueryHistory', () => {
});
describe('the historydetails panel', () => {
- let copyAllButton;
+ let copyAllButton, copyEditorButton;
beforeEach(() => {
- copyAllButton = () => queryDetail.find('#history-detail-query > button');
+ copyAllButton = () => queryDetail.find('#history-detail-query .btn-copy');
+ copyEditorButton = () => queryDetail.find('#history-detail-query .btn-copy-editor');
});
it('should change preferences', ()=>{
@@ -115,7 +118,7 @@ describe('QueryHistory', () => {
});
it('displays the formatted timestamp', () => {
- expect(queryDetail.text()).toContain('6-3-17 14:03:15');
+ expect(queryDetail.text()).toContain(moment(new Date(2017, 5, 3, 14, 3, 15, 150)).format('L HH:mm:ss'));
});
it('displays the number of rows affected', () => {
@@ -141,7 +144,7 @@ describe('QueryHistory', () => {
}, 1000);
});
- describe('when the "Copy All" button is clicked', () => {
+ describe('when the "Copy" button is clicked', () => {
beforeEach(() => {
spyOn(clipboard, 'copyTextToClipboard');
copyAllButton().trigger('click');
@@ -161,8 +164,8 @@ describe('QueryHistory', () => {
jasmine.clock().uninstall();
});
- it('should have text \'Copy All\'', () => {
- expect(copyAllButton().text()).toBe('Copy All');
+ it('should have text \'Copy\'', () => {
+ expect(copyAllButton().text()).toBe('Copy');
});
it('should not have the class \'was-copied\'', () => {
@@ -193,8 +196,8 @@ describe('QueryHistory', () => {
jasmine.clock().tick(1501);
});
- it('should change the button text back to \'Copy All\'', () => {
- expect(copyAllButton().text()).toBe('Copy All');
+ it('should change the button text back to \'Copy\'', () => {
+ expect(copyAllButton().text()).toBe('Copy');
});
});
@@ -224,14 +227,25 @@ describe('QueryHistory', () => {
jasmine.clock().tick(1501);
});
- it('should change the button text back to \'Copy All\'', () => {
- expect(copyAllButton().text()).toBe('Copy All');
+ it('should change the button text back to \'Copy\'', () => {
+ expect(copyAllButton().text()).toBe('Copy');
});
});
});
});
});
+ describe('when the "Copy to query editor" button is clicked', () => {
+ beforeEach(() => {
+ spyOn(historyComponent.queryHistDetails, 'onCopyToEditorHandler').and.callThrough();
+ copyEditorButton().trigger('click');
+ });
+
+ it('sends the query to the onCopyToEditorHandler', () => {
+ expect(historyComponent.queryHistDetails.onCopyToEditorHandler).toHaveBeenCalledWith('first sql statement');
+ });
+ });
+
describe('when the query failed', () => {
let failedEntry;