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;