mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Remove the large and unnecessary dependency on React and 87 other related libraries. Fixes #4018
This commit is contained in:
parent
d7bf6ec69f
commit
4b895941b3
@ -10,6 +10,7 @@ Features
|
||||
********
|
||||
|
||||
| `Feature #2001 <https://redmine.postgresql.org/issues/2001>`_ - Add support for reverse proxied setups with Gunicorn, and document Gunicorn, uWSGI & NGINX configurations.
|
||||
| `Feature #4018 <https://redmine.postgresql.org/issues/4018>`_ - Remove the large and unnecessary dependency on React and 87 other related libraries.
|
||||
|
||||
Bug fixes
|
||||
*********
|
||||
|
364
libraries.txt
364
libraries.txt
File diff suppressed because it is too large
Load Diff
@ -16,17 +16,14 @@ module.exports = {
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
"plugin:react/recommended",
|
||||
],
|
||||
'parserOptions': {
|
||||
'ecmaFeatures': {
|
||||
'experimentalObjectRestSpread': true,
|
||||
'jsx': true
|
||||
},
|
||||
'sourceType': 'module'
|
||||
},
|
||||
'plugins': [
|
||||
'react'
|
||||
],
|
||||
'globals': {
|
||||
'_': true,
|
||||
|
@ -28,14 +28,13 @@ module.exports = function (config) {
|
||||
* filename: null, // if no value is provided the sourcemap is inlined
|
||||
*/
|
||||
filename: '[name].js.map',
|
||||
test: /\.jsx?$/i, // process .js and .jsx files only
|
||||
test: /\.js$/i, // process .js files only
|
||||
}),
|
||||
],
|
||||
files: [
|
||||
'pgadmin/static/bundle/slickgrid.js',
|
||||
{pattern: 'pgadmin/static/**/*.js', included: false, watched: true},
|
||||
{pattern: 'pgadmin/browser/static/js/**/*.js', included: false, watched: true},
|
||||
'regression/javascript/**/*.jsx',
|
||||
{pattern: 'regression/javascript/**/*.js', watched: true},
|
||||
],
|
||||
|
||||
@ -48,9 +47,8 @@ module.exports = function (config) {
|
||||
// preprocess matching files before serving them to the browser
|
||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||
preprocessors: {
|
||||
'pgadmin/**/js/**/*.js[x]?': ['sourcemap'],
|
||||
'pgadmin/**/js/**/*.js?': ['sourcemap'],
|
||||
'regression/javascript/**/*.js': ['webpack', 'sourcemap'],
|
||||
'regression/javascript/**/*.jsx': ['webpack', 'sourcemap'],
|
||||
'pgadmin/static/bundle/slickgrid.js': ['webpack', 'sourcemap'],
|
||||
},
|
||||
|
||||
|
@ -5,22 +5,15 @@
|
||||
"babel-core": "~6.24.0",
|
||||
"babel-loader": "~7.1.2",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-airbnb": "^2.4.0",
|
||||
"babel-preset-es2015": "~6.24.0",
|
||||
"babel-preset-react": "~6.23.0",
|
||||
"cross-env": "^5.0.1",
|
||||
"eclint": "^2.3.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-matchers": "^4.1.1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^0.11.2",
|
||||
"image-webpack-loader": "^3.3.1",
|
||||
"is-docker": "^1.1.0",
|
||||
"jasmine-core": "~2.99.0",
|
||||
"jasmine-enzyme": "~4.1.1",
|
||||
"karma": "~2.0.0",
|
||||
"karma-babel-preprocessor": "^7.0.0",
|
||||
"karma-browserify": "~5.2.0",
|
||||
@ -82,15 +75,13 @@
|
||||
"moment-timezone": "^0.5.21",
|
||||
"mousetrap": "^1.6.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-split-pane": "^0.1.63",
|
||||
"requirejs": "~2.3.3",
|
||||
"select2": "^4.0.6-rc.1",
|
||||
"shim-loader": "^1.0.1",
|
||||
"slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.16",
|
||||
"snapsvg": "^0.5.1",
|
||||
"spectrum-colorpicker": "^1.8.0",
|
||||
"split.js": "^1.5.10",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tablesorter": "^2.30.6",
|
||||
"tempusdominus-bootstrap-4": "^5.1.2",
|
||||
@ -102,7 +93,7 @@
|
||||
"wkx": "^0.4.5"
|
||||
},
|
||||
"scripts": {
|
||||
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js --ext .jsx .",
|
||||
"linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js .",
|
||||
"webpacker": "yarn run webpack --config webpack.config.js --progress",
|
||||
"webpacker:watch": "yarn run webpack --config webpack.config.js --progress --watch",
|
||||
"bundle:watch": "yarn run linter && yarn run webpacker:watch",
|
||||
|
@ -10,8 +10,8 @@
|
||||
export default class HistoryCollection {
|
||||
|
||||
constructor(history_model) {
|
||||
this.historyList = history_model;
|
||||
this.onChange(() => {});
|
||||
this.historyList = _.sortBy(history_model, o=>o.start_time);
|
||||
this.onAdd(() => {});
|
||||
}
|
||||
|
||||
length() {
|
||||
@ -19,8 +19,10 @@ export default class HistoryCollection {
|
||||
}
|
||||
|
||||
add(object) {
|
||||
this.historyList.push(object);
|
||||
this.onChangeHandler(this.historyList);
|
||||
/* add object in sorted order */
|
||||
let pushAt = _.sortedIndex(this.historyList, object, o=>o.start_time);
|
||||
this.historyList.splice(pushAt, 0, object);
|
||||
this.onAddHandler(object);
|
||||
}
|
||||
|
||||
reset() {
|
||||
@ -28,8 +30,8 @@ export default class HistoryCollection {
|
||||
this.onResetHandler(this.historyList);
|
||||
}
|
||||
|
||||
onChange(onChangeHandler) {
|
||||
this.onChangeHandler = onChangeHandler;
|
||||
onAdd(onAddHandler) {
|
||||
this.onAddHandler = onAddHandler;
|
||||
}
|
||||
|
||||
onReset(onResetHandler) {
|
81
web/pgadmin/static/js/sqleditor/history/query_history.js
Normal file
81
web/pgadmin/static/js/sqleditor/history/query_history.js
Normal file
@ -0,0 +1,81 @@
|
||||
import QueryHistoryDetails from './query_history_details';
|
||||
import { QueryHistoryEntries } from './query_history_entries';
|
||||
import Split from 'split.js';
|
||||
import gettext from 'sources/gettext';
|
||||
import $ from 'jquery';
|
||||
|
||||
export default class QueryHistory {
|
||||
constructor(parentNode, histModel) {
|
||||
this.parentNode = parentNode;
|
||||
this.histCollection = histModel;
|
||||
this.editorPref = {};
|
||||
|
||||
this.histCollection.onAdd(this.onAddEntry.bind(this));
|
||||
this.histCollection.onReset(this.onResetEntries.bind(this));
|
||||
}
|
||||
|
||||
focus() {
|
||||
if (this.queryHistEntries) {
|
||||
this.queryHistEntries.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onAddEntry(entry) {
|
||||
if (this.histCollection.length() == 1) {
|
||||
this.render();
|
||||
} else if (this.queryHistEntries) {
|
||||
this.queryHistEntries.addEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
onResetEntries() {
|
||||
this.isRendered = false;
|
||||
this.queryHistEntries = null;
|
||||
this.queryHistDetails = null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
setEditorPref(editorPref) {
|
||||
this.editorPref = editorPref;
|
||||
if(this.queryHistDetails) {
|
||||
this.queryHistDetails.setEditorPref(this.editorPref);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.histCollection.length() == 0) {
|
||||
this.parentNode.empty()
|
||||
.removeClass('d-flex')
|
||||
.append(
|
||||
`<div class='alert alert-info pg-panel-message'>${gettext(
|
||||
'No history found'
|
||||
)}</div>`
|
||||
);
|
||||
} else {
|
||||
this.parentNode.empty().addClass('d-flex');
|
||||
let $histEntries = $('<div><div>').appendTo(this.parentNode);
|
||||
let $histDetails = $('<div><div>').appendTo(this.parentNode);
|
||||
|
||||
Split([$histEntries[0], $histDetails[0]], {
|
||||
gutterSize: 1,
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
|
||||
this.queryHistDetails = new QueryHistoryDetails($histDetails);
|
||||
this.queryHistDetails.setEditorPref(this.editorPref);
|
||||
this.queryHistDetails.render();
|
||||
|
||||
this.queryHistEntries = new QueryHistoryEntries($histEntries);
|
||||
this.queryHistEntries.onSelectedChange(
|
||||
(entry => {
|
||||
this.queryHistDetails.setEntry(entry);
|
||||
}).bind(this)
|
||||
);
|
||||
this.queryHistEntries.render();
|
||||
|
||||
this.histCollection.historyList.map((entry)=>{
|
||||
this.queryHistEntries.addEntry(entry);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
177
web/pgadmin/static/js/sqleditor/history/query_history_details.js
Normal file
177
web/pgadmin/static/js/sqleditor/history/query_history_details.js
Normal file
@ -0,0 +1,177 @@
|
||||
import CodeMirror from 'bundled_codemirror';
|
||||
import clipboard from 'sources/selection/clipboard';
|
||||
import moment from 'moment';
|
||||
import $ from 'jquery';
|
||||
|
||||
export default class QueryHistoryDetails {
|
||||
constructor(parentNode) {
|
||||
this.parentNode = parentNode;
|
||||
this.isCopied = false;
|
||||
this.timeout = null;
|
||||
this.isRendered = false;
|
||||
this.sqlFontSize = null;
|
||||
|
||||
this.editorPref = {
|
||||
'sql_font_size': '1em',
|
||||
};
|
||||
}
|
||||
|
||||
setEntry(entry) {
|
||||
this.entry = entry;
|
||||
if (this.isRendered) {
|
||||
this.selectiveRender();
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
setEditorPref(editorPref={}) {
|
||||
this.editorPref = {
|
||||
...this.editorPref,
|
||||
...editorPref,
|
||||
};
|
||||
|
||||
if(this.query_codemirror) {
|
||||
$(this.query_codemirror.getWrapperElement()).css(
|
||||
'font-size',this.editorPref.sql_font_size
|
||||
);
|
||||
|
||||
this.query_codemirror.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
parseErrorMessage(message) {
|
||||
return message.match(/ERROR:\s*([^\n\r]*)/i)
|
||||
? message.match(/ERROR:\s*([^\n\r]*)/i)[1]
|
||||
: message;
|
||||
}
|
||||
|
||||
formatDate(date) {
|
||||
return moment(date).format('M-D-YY HH:mm:ss');
|
||||
}
|
||||
|
||||
copyAllHandler() {
|
||||
clipboard.copyTextToClipboard(this.entry.query);
|
||||
|
||||
this.clearPreviousTimeout();
|
||||
|
||||
this.updateCopyButton(true);
|
||||
|
||||
this.timeout = setTimeout(() => {
|
||||
this.updateCopyButton(false);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
clearPreviousTimeout() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
updateCopyButton(copied) {
|
||||
if (copied) {
|
||||
this.$copyBtn.attr('class', 'was-copied');
|
||||
this.$copyBtn.text('Copied!');
|
||||
} else {
|
||||
this.$copyBtn.attr('class', 'copy-all');
|
||||
this.$copyBtn.text('Copy All');
|
||||
}
|
||||
}
|
||||
|
||||
updateQueryMetaData() {
|
||||
let itemTemplate = (data, description) => {
|
||||
return `<div class='item'>
|
||||
<span class='value'>${data}</span>
|
||||
<span class='description'>${description}</span>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
this.$metaData.empty().append(
|
||||
`<div class='metadata'>
|
||||
${itemTemplate(this.formatDate(this.entry.start_time), 'Date')}
|
||||
${itemTemplate(
|
||||
this.entry.row_affected.toLocaleString(),
|
||||
'Rows Affected'
|
||||
)}
|
||||
${itemTemplate(this.entry.total_time, 'Duration')}
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
updateMessageContent() {
|
||||
this.$message_content
|
||||
.empty()
|
||||
.append(`<pre class='content-value'>${this.entry.message}</pre>`);
|
||||
}
|
||||
|
||||
updateErrorMessage() {
|
||||
if (!this.entry.status) {
|
||||
this.$errMsgBlock.removeClass('d-none');
|
||||
this.$errMsgBlock.empty().append(
|
||||
`<div class='history-error-text'>
|
||||
<span>Error Message</span> ${this.parseErrorMessage(
|
||||
this.entry.message
|
||||
)}
|
||||
</div>`
|
||||
);
|
||||
} else {
|
||||
this.$errMsgBlock.addClass('d-none');
|
||||
this.$errMsgBlock.empty();
|
||||
}
|
||||
}
|
||||
|
||||
selectiveRender() {
|
||||
this.updateErrorMessage();
|
||||
this.updateCopyButton(false);
|
||||
this.updateQueryMetaData();
|
||||
this.query_codemirror.setValue(this.entry.query);
|
||||
this.updateMessageContent();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.entry) {
|
||||
this.parentNode.empty().append(
|
||||
`<div id='query_detail' class='query-detail'>
|
||||
<div class='error-message-block'></div>
|
||||
<div class='metadata-block'></div>
|
||||
<div class='query-statement-block'>
|
||||
<div id='history-detail-query'>
|
||||
<button class='' tabindex=0 accesskey='y'></button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<hr class='block-divider'/>
|
||||
</div>
|
||||
<div class='message-block'>
|
||||
<div class='message'>
|
||||
<div class='message-header'>Messages</div>
|
||||
<div class='content'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
|
||||
this.$errMsgBlock = this.parentNode.find('.error-message-block');
|
||||
this.$copyBtn = this.parentNode.find('#history-detail-query button');
|
||||
this.$copyBtn.off('click').on('click', this.copyAllHandler.bind(this));
|
||||
this.$metaData = this.parentNode.find('.metadata-block');
|
||||
this.query_codemirror = CodeMirror(
|
||||
this.parentNode.find('#history-detail-query div')[0],
|
||||
{
|
||||
tabindex: -1,
|
||||
mode: 'text/x-pgsql',
|
||||
readOnly: true,
|
||||
}
|
||||
);
|
||||
$(this.query_codemirror.getWrapperElement()).css(
|
||||
'font-size',this.editorPref.sql_font_size
|
||||
);
|
||||
this.$message_content = this.parentNode.find('.message-block .content');
|
||||
|
||||
this.isRendered = true;
|
||||
this.selectiveRender();
|
||||
}
|
||||
}
|
||||
}
|
230
web/pgadmin/static/js/sqleditor/history/query_history_entries.js
Normal file
230
web/pgadmin/static/js/sqleditor/history/query_history_entries.js
Normal file
@ -0,0 +1,230 @@
|
||||
import moment from 'moment';
|
||||
import $ from 'jquery';
|
||||
|
||||
const ARROWUP = 38;
|
||||
const ARROWDOWN = 40;
|
||||
|
||||
export class QueryHistoryEntryDateGroup {
|
||||
constructor(date, groupKey) {
|
||||
this.date = date;
|
||||
this.formatString = 'MMM DD YYYY';
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
getDatePrefix() {
|
||||
let prefix = '';
|
||||
if (this.isDaysBefore(0)) {
|
||||
prefix = 'Today - ';
|
||||
} else if (this.isDaysBefore(1)) {
|
||||
prefix = 'Yesterday - ';
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
getDateFormatted(momentToFormat) {
|
||||
return momentToFormat.format(this.formatString);
|
||||
}
|
||||
|
||||
getDateMoment() {
|
||||
return moment(this.date);
|
||||
}
|
||||
|
||||
isDaysBefore(before) {
|
||||
return (
|
||||
this.getDateFormatted(this.getDateMoment()) ===
|
||||
this.getDateFormatted(moment().subtract(before, 'days'))
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return $(`<div class='query-group' data-key='${this.groupKey}'>
|
||||
<div class='date-label'>${this.getDatePrefix()}${this.getDateFormatted(
|
||||
this.getDateMoment()
|
||||
)}</div>
|
||||
<ul class='query-entries'></ul>
|
||||
</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryHistoryItem {
|
||||
constructor(entry) {
|
||||
this.entry = entry;
|
||||
this.$el = null;
|
||||
this.onClickHandler = null;
|
||||
}
|
||||
|
||||
onClick(onClickHandler) {
|
||||
this.onClickHandler = onClickHandler;
|
||||
if (this.$el) {
|
||||
this.$el.off('click').on('click', e => {
|
||||
this.onClickHandler($(e.currentTarget));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formatDate(date) {
|
||||
return moment(date).format('HH:mm:ss');
|
||||
}
|
||||
|
||||
render() {
|
||||
this.$el = $(
|
||||
`<li class='list-item' tabindex='0' data-key='${this.formatDate(this.entry.start_time)}'>
|
||||
<div class='entry ${this.entry.status ? '' : 'error'}'>
|
||||
<div class='query'>${this.entry.query}</div>
|
||||
<div class='other-info'>
|
||||
<div class='timestamp'>${this.formatDate(this.entry.start_time)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>`
|
||||
)
|
||||
.data('entrydata', this.entry)
|
||||
.on('click', e => {
|
||||
this.onClickHandler($(e.currentTarget));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryHistoryEntries {
|
||||
constructor(parentNode) {
|
||||
this.parentNode = parentNode;
|
||||
this.$selectedItem = null;
|
||||
this.groupKeyFormat = 'YYYY MM DD';
|
||||
|
||||
this.$el = null;
|
||||
}
|
||||
|
||||
onSelectedChange(onSelectedChangeHandler) {
|
||||
this.onSelectedChangeHandler = onSelectedChangeHandler;
|
||||
}
|
||||
|
||||
focus() {
|
||||
let self = this;
|
||||
|
||||
if (!this.$selectedItem) {
|
||||
this.setSelectedListItem(this.$el.find('.list-item').first());
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
self.$selectedItem.trigger('click');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
isArrowDown(event) {
|
||||
return (event.keyCode || event.which) === ARROWDOWN;
|
||||
}
|
||||
|
||||
isArrowUp(event) {
|
||||
return (event.keyCode || event.which) === ARROWUP;
|
||||
}
|
||||
|
||||
navigateUpAndDown(event) {
|
||||
let arrowKeys = [ARROWUP, ARROWDOWN];
|
||||
let key = event.keyCode || event.which;
|
||||
if (arrowKeys.indexOf(key) > -1) {
|
||||
event.preventDefault();
|
||||
this.onKeyDownHandler(event);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onKeyDownHandler(event) {
|
||||
if (this.isArrowDown(event)) {
|
||||
if (this.$selectedItem.next().length > 0) {
|
||||
this.setSelectedListItem(this.$selectedItem.next());
|
||||
} else {
|
||||
/* if last, jump to next group */
|
||||
let $group = this.$selectedItem.closest('.query-group');
|
||||
if ($group.next().length > 0) {
|
||||
this.setSelectedListItem(
|
||||
$group.next().find('.list-item').first()
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (this.isArrowUp(event)) {
|
||||
if (this.$selectedItem.prev().length > 0) {
|
||||
this.setSelectedListItem(this.$selectedItem.prev());
|
||||
} else {
|
||||
/* if first, jump to prev group */
|
||||
let $group = this.$selectedItem.closest('.query-group');
|
||||
if ($group.prev().length > 0) {
|
||||
this.setSelectedListItem(
|
||||
$group.prev().find('.list-item').last()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSelectListItem(event) {
|
||||
this.setSelectedListItem($(event.currentTarget));
|
||||
}
|
||||
|
||||
dateAsGroupKey(date) {
|
||||
return moment(date).format(this.groupKeyFormat);
|
||||
}
|
||||
|
||||
setSelectedListItem($listItem) {
|
||||
if (this.$selectedItem) {
|
||||
this.$selectedItem.removeClass('selected');
|
||||
}
|
||||
$listItem.addClass('selected');
|
||||
this.$selectedItem = $listItem;
|
||||
this.$selectedItem[0].scrollIntoView(false);
|
||||
|
||||
if (this.onSelectedChangeHandler) {
|
||||
this.onSelectedChangeHandler(this.$selectedItem.data('entrydata'));
|
||||
}
|
||||
}
|
||||
|
||||
addEntry(entry) {
|
||||
/* Add the entry in respective date group in descending sorted order. */
|
||||
let groups = this.$el.find('.query-group');
|
||||
let groupsKeys = $.map(groups, group => {
|
||||
return $(group).attr('data-key');
|
||||
});
|
||||
let entryGroupKey = this.dateAsGroupKey(entry.start_time);
|
||||
let groupIdx = _.indexOf(groupsKeys, entryGroupKey);
|
||||
|
||||
let $groupEl = null;
|
||||
/* if no groups present */
|
||||
if (groups.length == 0) {
|
||||
$groupEl = new QueryHistoryEntryDateGroup(
|
||||
entry.start_time,
|
||||
entryGroupKey
|
||||
).render();
|
||||
this.$el.prepend($groupEl);
|
||||
} else if (groupIdx < 0 && groups.length != 0) {
|
||||
/* if groups are present, but this is a new group */
|
||||
$groupEl = new QueryHistoryEntryDateGroup(
|
||||
entry.start_time,
|
||||
entryGroupKey
|
||||
).render();
|
||||
if (groups[groupIdx]) {
|
||||
$groupEl.insertBefore(groups[groupIdx]);
|
||||
} else {
|
||||
this.$el.prepend($groupEl);
|
||||
}
|
||||
} else if (groupIdx >= 0) {
|
||||
/* if groups present, but this is a new one */
|
||||
$groupEl = $(groups[groupIdx]);
|
||||
}
|
||||
|
||||
let newItem = new QueryHistoryItem(entry);
|
||||
newItem.onClick(this.setSelectedListItem.bind(this));
|
||||
newItem.render();
|
||||
|
||||
$groupEl.find('.query-entries').prepend(newItem.$el);
|
||||
this.setSelectedListItem(newItem.$el);
|
||||
}
|
||||
|
||||
render() {
|
||||
let self = this;
|
||||
self.$el = $(`
|
||||
<div id='query_list' class='query-history' tabindex='0'>
|
||||
</div>
|
||||
`).on('keydown', this.navigateUpAndDown.bind(this));
|
||||
|
||||
self.parentNode.empty().append(self.$el);
|
||||
}
|
||||
}
|
@ -173,7 +173,9 @@ function updateUIPreferences(sqlEditor) {
|
||||
sqlEditor.query_tool_obj.refresh();
|
||||
|
||||
/* Render history to reflect Font size change */
|
||||
sqlEditor.render_history_grid();
|
||||
sqlEditor.historyComponent.setEditorPref({
|
||||
'sql_font_size' : sql_font_size,
|
||||
});
|
||||
}
|
||||
|
||||
export {updateUIPreferences};
|
||||
|
@ -1,66 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import $ from 'jquery';
|
||||
import code_mirror from 'sources/../bundle/codemirror';
|
||||
|
||||
export default class CodeMirror extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
shouldHydrate: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.editor = code_mirror(this.container);
|
||||
this.hydrateInterval = setInterval(this.hydrateWhenBecomesVisible.bind(this), 100);
|
||||
this.hydrate(this.props);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.hydrateInterval);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.hydrate(nextProps);
|
||||
}
|
||||
|
||||
hydrateWhenBecomesVisible() {
|
||||
const isVisible = $(this.container).is(':visible');
|
||||
|
||||
if (isVisible && this.state.shouldHydrate) {
|
||||
this.hydrate(this.props);
|
||||
this.setState({shouldHydrate: false});
|
||||
} else if (!isVisible) {
|
||||
this.setState({shouldHydrate: true});
|
||||
}
|
||||
}
|
||||
|
||||
hydrate(props) {
|
||||
Object.keys(props.options || {}).forEach(key => this.editor.setOption(key, props.options[key]));
|
||||
|
||||
this.editor.setValue(props.value || '');
|
||||
|
||||
if(props.sqlFontSize) {
|
||||
$(this.editor.getWrapperElement()).css('font-size', props.sqlFontSize);
|
||||
}
|
||||
|
||||
this.editor.refresh();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div ref={(self) => this.container = self}/>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Shapes from '../../react_shapes';
|
||||
|
||||
export default class HistoryDetailMessage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='message'>
|
||||
<div className='message-header'>
|
||||
Messages
|
||||
</div>
|
||||
<div className='content'>
|
||||
<pre className='content-value'>
|
||||
{this.props.historyEntry.message}
|
||||
</pre>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailMessage.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import Shapes from '../../react_shapes';
|
||||
|
||||
export default class HistoryDetailMetadata extends React.Component {
|
||||
|
||||
formatDate(date) {
|
||||
return (moment(date).format('M-D-YY HH:mm:ss'));
|
||||
}
|
||||
|
||||
queryMetaData(data, description) {
|
||||
return <div className='item'>
|
||||
<span className='value'>
|
||||
{data}
|
||||
</span>
|
||||
<span className='description'>
|
||||
{description}
|
||||
</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className='metadata'>
|
||||
{this.queryMetaData(this.formatDate(this.props.historyEntry.start_time), 'Date')}
|
||||
{this.queryMetaData(this.props.historyEntry.row_affected.toLocaleString(), 'Rows Affected')}
|
||||
{this.queryMetaData(this.props.historyEntry.total_time, 'Duration')}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailMetadata.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -1,76 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import 'codemirror/mode/sql/sql';
|
||||
|
||||
import CodeMirror from './code_mirror';
|
||||
import Shapes from '../../react_shapes';
|
||||
import clipboard from '../../../js/selection/clipboard';
|
||||
|
||||
export default class HistoryDetailQuery extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.copyAllHandler = this.copyAllHandler.bind(this);
|
||||
this.state = {isCopied: false};
|
||||
this.timeout = undefined;
|
||||
}
|
||||
|
||||
copyAllHandler() {
|
||||
clipboard.copyTextToClipboard(this.props.historyEntry.query);
|
||||
|
||||
this.clearPreviousTimeout();
|
||||
|
||||
this.setState({isCopied: true});
|
||||
this.timeout = setTimeout(() => {
|
||||
this.setState({isCopied: false});
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
clearPreviousTimeout() {
|
||||
if (this.timeout !== undefined) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
copyButtonText() {
|
||||
return this.state.isCopied ? 'Copied!' : 'Copy All';
|
||||
}
|
||||
|
||||
copyButtonClass() {
|
||||
return this.state.isCopied ? 'was-copied' : 'copy-all';
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="history-detail-query">
|
||||
<button className={this.copyButtonClass()}
|
||||
tabIndex={0}
|
||||
accessKey={'y'}
|
||||
onClick={this.copyAllHandler}>{this.copyButtonText()}</button>
|
||||
<CodeMirror
|
||||
value={this.props.historyEntry.query}
|
||||
options={{
|
||||
tabindex: -1,
|
||||
mode: 'text/x-pgsql',
|
||||
readOnly: true,
|
||||
}}
|
||||
sqlFontSize= {this.props.sqlEditorPref.sql_font_size}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryDetailQuery.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
sqlEditorPref: Shapes.sqlEditorPrefObj,
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Shapes from '../../react_shapes';
|
||||
|
||||
export default class HistoryErrorMessage extends React.Component {
|
||||
|
||||
parseErrorMessage(message) {
|
||||
return message.match(/ERROR:\s*([^\n\r]*)/i) ?
|
||||
message.match(/ERROR:\s*([^\n\r]*)/i)[1] :
|
||||
message;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='history-error-text'>
|
||||
<span>Error Message</span> {this.parseErrorMessage(this.props.historyEntry.message)}
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryErrorMessage.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -1,121 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import SplitPane from 'react-split-pane';
|
||||
import _ from 'underscore';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
import QueryHistoryDetail from './query_history_detail';
|
||||
import QueryHistoryEntries from './query_history_entries';
|
||||
import Shapes from '../react_shapes';
|
||||
|
||||
const queryEntryListDivStyle = {
|
||||
overflowY: 'auto',
|
||||
};
|
||||
const queryDetailDivStyle = {
|
||||
display: 'flex',
|
||||
};
|
||||
|
||||
export default class QueryHistory extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
history: [],
|
||||
selectedEntry: 0,
|
||||
};
|
||||
|
||||
this.selectHistoryEntry = this.selectHistoryEntry.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setHistory(this.props.historyCollection.historyList);
|
||||
this.selectHistoryEntry(0);
|
||||
|
||||
this.props.historyCollection.onChange((historyList) => {
|
||||
this.setHistory(historyList);
|
||||
this.selectHistoryEntry(0);
|
||||
});
|
||||
|
||||
this.props.historyCollection.onReset(() => {
|
||||
this.setState({
|
||||
history: [],
|
||||
currentHistoryDetail: undefined,
|
||||
selectedEntry: 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.selectHistoryEntry(0);
|
||||
}
|
||||
|
||||
refocus() {
|
||||
if (this.state.history.length > 0) {
|
||||
setTimeout(() => this.retrieveSelectedQuery().parentElement.focus(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
retrieveSelectedQuery() {
|
||||
return ReactDOM.findDOMNode(this)
|
||||
.getElementsByClassName('selected')[0];
|
||||
}
|
||||
|
||||
setHistory(historyList) {
|
||||
this.setState({history: this.orderedHistory(historyList)});
|
||||
}
|
||||
|
||||
selectHistoryEntry(index) {
|
||||
this.setState({
|
||||
currentHistoryDetail: this.state.history[index],
|
||||
selectedEntry: index,
|
||||
});
|
||||
}
|
||||
|
||||
orderedHistory(historyList) {
|
||||
return _.chain(historyList)
|
||||
.sortBy(historyEntry => historyEntry.start_time)
|
||||
.reverse()
|
||||
.value();
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.state.history.length == 0) {
|
||||
return(
|
||||
<div className="alert alert-info pg-panel-message">{gettext('No history found')}</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<SplitPane defaultSize='50%' split='vertical' pane1Style={queryEntryListDivStyle}
|
||||
pane2Style={queryDetailDivStyle}>
|
||||
<QueryHistoryEntries historyEntries={this.state.history}
|
||||
selectedEntry={this.state.selectedEntry}
|
||||
onSelectEntry={this.selectHistoryEntry}
|
||||
/>
|
||||
<QueryHistoryDetail historyEntry={this.state.currentHistoryDetail}
|
||||
sqlEditorPref={this.props.sqlEditorPref}
|
||||
/>
|
||||
</SplitPane>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistory.propTypes = {
|
||||
historyCollection: Shapes.historyCollectionClass.isRequired,
|
||||
sqlEditorPref: Shapes.sqlEditorPrefObj,
|
||||
};
|
||||
|
||||
export {
|
||||
QueryHistory,
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import HistoryDetailMetadata from './detail/history_detail_metadata';
|
||||
import HistoryDetailQuery from './detail/history_detail_query';
|
||||
import HistoryDetailMessage from './detail/history_detail_message';
|
||||
import HistoryErrorMessage from './detail/history_error_message';
|
||||
import Shapes from '../react_shapes';
|
||||
|
||||
export default class QueryHistoryDetail extends React.Component {
|
||||
|
||||
render() {
|
||||
if (!_.isUndefined(this.props.historyEntry)) {
|
||||
let historyErrorMessage = null;
|
||||
if (!this.props.historyEntry.status) {
|
||||
historyErrorMessage = <div className='error-message-block'>
|
||||
<HistoryErrorMessage {...this.props} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='query_detail' className='query-detail'>
|
||||
{historyErrorMessage}
|
||||
<div className='metadata-block'>
|
||||
<HistoryDetailMetadata {...this.props} />
|
||||
</div>
|
||||
<div className='query-statement-block'>
|
||||
<HistoryDetailQuery {...this.props}/>
|
||||
</div>
|
||||
<div>
|
||||
<hr className='block-divider'/>
|
||||
</div>
|
||||
<div className='message-block'>
|
||||
<HistoryDetailMessage {...this.props}/>
|
||||
</div>
|
||||
</div>);
|
||||
} else {
|
||||
return <p></p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryDetail.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
};
|
@ -1,157 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import _ from 'underscore';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import QueryHistoryEntry from './query_history_entry';
|
||||
import QueryHistoryEntryDateGroup from './query_history_entry_date_group';
|
||||
|
||||
const ARROWUP = 38;
|
||||
const ARROWDOWN = 40;
|
||||
|
||||
export default class QueryHistoryEntries extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.navigateUpAndDown = this.navigateUpAndDown.bind(this);
|
||||
}
|
||||
|
||||
navigateUpAndDown(event) {
|
||||
let arrowKeys = [ARROWUP, ARROWDOWN];
|
||||
let key = event.keyCode || event.which;
|
||||
if (arrowKeys.indexOf(key) > -1) {
|
||||
event.preventDefault();
|
||||
this.onKeyDownHandler(event);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
onKeyDownHandler(event) {
|
||||
if (this.isArrowDown(event)) {
|
||||
if (!this.isLastEntry()) {
|
||||
let nextEntry = this.props.selectedEntry + 1;
|
||||
this.props.onSelectEntry(nextEntry);
|
||||
|
||||
if (this.isInvisible(this.getEntryFromList(nextEntry))) {
|
||||
this.getEntryFromList(nextEntry).scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
} else if (this.isArrowUp(event)) {
|
||||
if (!this.isFirstEntry()) {
|
||||
let previousEntry = this.props.selectedEntry - 1;
|
||||
this.props.onSelectEntry(previousEntry);
|
||||
|
||||
if (this.isInvisible(this.getEntryFromList(previousEntry))) {
|
||||
this.getEntryFromList(previousEntry).scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retrieveGroups() {
|
||||
const sortableKeyFormat = 'YYYY MM DD';
|
||||
const entriesGroupedByDate = _.groupBy(this.props.historyEntries, entry => moment(entry.start_time).format(sortableKeyFormat));
|
||||
|
||||
const elements = this.sortDesc(entriesGroupedByDate).map((key, index) => {
|
||||
const groupElements = this.retrieveDateGroup(entriesGroupedByDate, key, index);
|
||||
const keyAsDate = moment(key, sortableKeyFormat).toDate();
|
||||
groupElements.unshift(
|
||||
<li key={'group-' + index}>
|
||||
<QueryHistoryEntryDateGroup date={keyAsDate}/>
|
||||
</li>);
|
||||
return groupElements;
|
||||
});
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{_.flatten(elements).map(element => element)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
retrieveDateGroup(entriesGroupedByDate, key, parentIndex) {
|
||||
const startingEntryIndex = _.reduce(
|
||||
_.first(this.sortDesc(entriesGroupedByDate), parentIndex),
|
||||
(memo, key) => memo + entriesGroupedByDate[key].length, 0);
|
||||
|
||||
return (
|
||||
entriesGroupedByDate[key].map((entry, index) =>
|
||||
<li key={`group-${parentIndex}-entry-${index}`}
|
||||
className='list-item'
|
||||
tabIndex={0}
|
||||
onClick={() => this.props.onSelectEntry(startingEntryIndex + index)}
|
||||
onKeyDown={this.navigateUpAndDown}>
|
||||
<QueryHistoryEntry
|
||||
historyEntry={entry}
|
||||
isSelected={(startingEntryIndex + index) === this.props.selectedEntry}/>
|
||||
</li>)
|
||||
);
|
||||
}
|
||||
|
||||
sortDesc(entriesGroupedByDate) {
|
||||
return Object.keys(entriesGroupedByDate).sort().reverse();
|
||||
}
|
||||
|
||||
isInvisible(element) {
|
||||
return this.isAbovePaneTop(element) || this.isBelowPaneBottom(element);
|
||||
}
|
||||
|
||||
isArrowUp(event) {
|
||||
return (event.keyCode || event.which) === ARROWUP;
|
||||
}
|
||||
|
||||
isArrowDown(event) {
|
||||
return (event.keyCode || event.which) === ARROWDOWN;
|
||||
}
|
||||
|
||||
isFirstEntry() {
|
||||
return this.props.selectedEntry === 0;
|
||||
}
|
||||
|
||||
isLastEntry() {
|
||||
return this.props.selectedEntry === this.props.historyEntries.length - 1;
|
||||
}
|
||||
|
||||
isAbovePaneTop(element) {
|
||||
const paneElement = ReactDOM.findDOMNode(this).parentElement;
|
||||
return element.getBoundingClientRect().top < paneElement.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
isBelowPaneBottom(element) {
|
||||
const paneElement = ReactDOM.findDOMNode(this).parentElement;
|
||||
return element.getBoundingClientRect().bottom > paneElement.getBoundingClientRect().bottom;
|
||||
}
|
||||
|
||||
getEntryFromList(entryIndex) {
|
||||
return ReactDOM.findDOMNode(this).getElementsByClassName('entry')[entryIndex];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id='query_list'
|
||||
className="query-history">
|
||||
{this.retrieveGroups()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryEntries.propTypes = {
|
||||
historyEntries: PropTypes.array.isRequired,
|
||||
selectedEntry: PropTypes.number.isRequired,
|
||||
onSelectEntry: PropTypes.func.isRequired,
|
||||
};
|
@ -1,59 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import Shapes from '../react_shapes';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class QueryHistoryEntry extends React.Component {
|
||||
formatDate(date) {
|
||||
return (moment(date).format('HH:mm:ss'));
|
||||
}
|
||||
|
||||
renderWithClasses(outerDivStyle) {
|
||||
return (
|
||||
<div className={'entry ' + outerDivStyle}>
|
||||
<div className='query'>
|
||||
{this.props.historyEntry.query}
|
||||
</div>
|
||||
<div className='other-info'>
|
||||
<div className='timestamp'>
|
||||
{this.formatDate(this.props.historyEntry.start_time)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.hasError()) {
|
||||
if (this.props.isSelected) {
|
||||
return this.renderWithClasses('error selected');
|
||||
} else {
|
||||
return this.renderWithClasses('error');
|
||||
}
|
||||
} else {
|
||||
if (this.props.isSelected) {
|
||||
return this.renderWithClasses('selected');
|
||||
} else {
|
||||
return this.renderWithClasses('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasError() {
|
||||
return !this.props.historyEntry.status;
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryEntry.propTypes = {
|
||||
historyEntry: Shapes.historyDetail,
|
||||
isSelected: PropTypes.bool,
|
||||
};
|
@ -1,47 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default class QueryHistoryEntryDateGroup extends React.Component {
|
||||
|
||||
getDatePrefix() {
|
||||
let prefix = '';
|
||||
if (this.isDaysBefore(0)) {
|
||||
prefix = 'Today - ';
|
||||
} else if (this.isDaysBefore(1)) {
|
||||
prefix = 'Yesterday - ';
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
getDateFormatted(momentToFormat) {
|
||||
return momentToFormat.format(QueryHistoryEntryDateGroup.formatString);
|
||||
}
|
||||
|
||||
getDateMoment() {
|
||||
return moment(this.props.date);
|
||||
}
|
||||
|
||||
isDaysBefore(before) {
|
||||
return this.getDateFormatted(this.getDateMoment()) === this.getDateFormatted(moment().subtract(before, 'days'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="date-label">{this.getDatePrefix()}{this.getDateFormatted(this.getDateMoment())}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
QueryHistoryEntryDateGroup.propTypes = {
|
||||
date: PropTypes.instanceOf(Date).isRequired,
|
||||
};
|
||||
|
||||
QueryHistoryEntryDateGroup.formatString = 'MMM DD YYYY';
|
@ -1,37 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
let historyDetail =
|
||||
PropTypes.shape({
|
||||
query: PropTypes.string,
|
||||
start_time: PropTypes.instanceOf(Date),
|
||||
status: PropTypes.bool,
|
||||
total_time: PropTypes.string,
|
||||
row_affected: PropTypes.int,
|
||||
message: PropTypes.string,
|
||||
});
|
||||
|
||||
let historyCollectionClass =
|
||||
PropTypes.shape({
|
||||
historyList: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
});
|
||||
|
||||
let sqlEditorPrefObj =
|
||||
PropTypes.shape({
|
||||
sql_font_size: PropTypes.string.isRequired,
|
||||
});
|
||||
|
||||
export default {
|
||||
historyDetail,
|
||||
historyCollectionClass,
|
||||
sqlEditorPrefObj,
|
||||
};
|
@ -25,9 +25,8 @@ define('tools.querytool', [
|
||||
'sources/sqleditor/query_tool_http_error_handler',
|
||||
'sources/sqleditor/filter_dialog',
|
||||
'sources/sqleditor/geometry_viewer',
|
||||
'sources/history/index.js',
|
||||
'sourcesjsx/history/query_history',
|
||||
'react', 'react-dom',
|
||||
'sources/sqleditor/history/history_collection.js',
|
||||
'sources/sqleditor/history/query_history',
|
||||
'sources/keyboard_shortcuts',
|
||||
'sources/sqleditor/query_tool_actions',
|
||||
'sources/sqleditor/query_tool_notifications',
|
||||
@ -48,7 +47,7 @@ define('tools.querytool', [
|
||||
babelPollyfill, gettext, url_for, $, jqueryui, jqueryui_position, _, S, alertify, pgAdmin, Backbone, codemirror,
|
||||
pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent,
|
||||
XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler,
|
||||
GeometryViewer, HistoryBundle, queryHistory, React, ReactDOM,
|
||||
GeometryViewer, historyColl, queryHist,
|
||||
keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid,
|
||||
modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref) {
|
||||
/* Return back, this has been called more than once */
|
||||
@ -60,7 +59,9 @@ define('tools.querytool', [
|
||||
var wcDocker = window.wcDocker,
|
||||
pgBrowser = pgAdmin.Browser,
|
||||
CodeMirror = codemirror.default,
|
||||
Slick = window.Slick;
|
||||
Slick = window.Slick,
|
||||
HistoryCollection = historyColl.default,
|
||||
QueryHistory = queryHist.default;
|
||||
|
||||
var is_query_running = false;
|
||||
|
||||
@ -1303,25 +1304,17 @@ define('tools.querytool', [
|
||||
|
||||
/* Should not reset if function called again */
|
||||
if(!self.history_collection) {
|
||||
self.history_collection = new HistoryBundle.HistoryCollection([]);
|
||||
self.history_collection = new HistoryCollection([]);
|
||||
}
|
||||
|
||||
var historyComponent;
|
||||
var historyCollectionReactElement = React.createElement(
|
||||
queryHistory.QueryHistory, {
|
||||
historyCollection: self.history_collection,
|
||||
ref: function(component) {
|
||||
historyComponent = component;
|
||||
},
|
||||
sqlEditorPref: {
|
||||
sql_font_size: SqlEditorUtils.calcFontSize(this.preferences.sql_font_size),
|
||||
},
|
||||
});
|
||||
ReactDOM.render(historyCollectionReactElement, $('#history_grid')[0]);
|
||||
if(!self.historyComponent) {
|
||||
self.historyComponent = new QueryHistory($('#history_grid'), self.history_collection);
|
||||
self.historyComponent.render();
|
||||
}
|
||||
|
||||
self.history_panel.off(wcDocker.EVENT.VISIBILITY_CHANGED);
|
||||
self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() {
|
||||
historyComponent.refocus();
|
||||
self.historyComponent.focus();
|
||||
});
|
||||
},
|
||||
|
||||
@ -2574,6 +2567,12 @@ define('tools.querytool', [
|
||||
$('#btn-flash').prop('disabled', false);
|
||||
$('#btn-download').prop('disabled', false);
|
||||
self.trigger('pgadmin-sqleditor:loading-icon:hide');
|
||||
|
||||
if(!self.total_time) {
|
||||
self.total_time = calculateQueryRunTime.calculateQueryRunTime(
|
||||
self.query_start_time,
|
||||
new Date());
|
||||
}
|
||||
self.gridView.history_collection.add({
|
||||
'status': status,
|
||||
'start_time': self.query_start_time,
|
||||
|
@ -1,5 +1,6 @@
|
||||
.query-history {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
.list-item {
|
||||
border-bottom: $panel-border;
|
||||
background-color: $color-bg-theme;
|
||||
@ -46,19 +47,21 @@
|
||||
background: $sql-history-error-bg;
|
||||
}
|
||||
|
||||
.entry.selected.error {
|
||||
background-color: $sql-history-error-bg;
|
||||
}
|
||||
.selected {
|
||||
& .entry.error {
|
||||
background-color: $sql-history-error-bg;
|
||||
}
|
||||
|
||||
.entry.selected {
|
||||
color: $sql-history-success-fg;
|
||||
border: $table-hover-border;
|
||||
background: $sql-history-success-bg;
|
||||
font-weight: bold;
|
||||
|
||||
.other-info {
|
||||
& .entry {
|
||||
color: $sql-history-success-fg;
|
||||
border: $table-hover-border;
|
||||
background: $sql-history-success-bg;
|
||||
font-weight: bold;
|
||||
|
||||
.other-info {
|
||||
color: $sql-history-success-fg;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,6 +89,7 @@
|
||||
|
||||
|
||||
.query-detail {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -205,3 +209,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#history_grid {
|
||||
.gutter.gutter-horizontal {
|
||||
width: $panel-border-width;
|
||||
background: $panel-border-color;
|
||||
|
||||
&:hover {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import React from 'react';
|
||||
import $ from 'jquery';
|
||||
import CodeMirror from '../../pgadmin/static/jsx/history/detail/code_mirror';
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import './helper/enzyme.helper';
|
||||
|
||||
describe('CodeMirror', () => {
|
||||
beforeEach(() => {
|
||||
jasmineEnzyme();
|
||||
});
|
||||
|
||||
describe('#hydrateWhenBecomesVisible', () => {
|
||||
let codeMirror, isVisibleSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
codeMirror = shallow(<CodeMirror />).instance();
|
||||
isVisibleSpy = spyOn($.fn, 'is');
|
||||
spyOn(codeMirror, 'hydrate');
|
||||
});
|
||||
|
||||
describe('when component is visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(true);
|
||||
});
|
||||
|
||||
it('should hydrate the codemirror element', () => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when component is not visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(false);
|
||||
});
|
||||
|
||||
it('should not hydrate the codemirror element', () => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when becomes visible', () => {
|
||||
beforeEach(() => {
|
||||
isVisibleSpy.and.returnValue(true);
|
||||
});
|
||||
|
||||
it('should hydrate the codemirror element', (done) => {
|
||||
setTimeout(() => {
|
||||
codeMirror.hydrateWhenBecomesVisible();
|
||||
expect(codeMirror.hydrate).toHaveBeenCalledTimes(1);
|
||||
done();
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,13 +0,0 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({adapter: new Adapter()});
|
@ -7,18 +7,17 @@
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
|
||||
import '../helper/enzyme.helper';
|
||||
import HistoryCollection from 'sources/sqleditor/history/history_collection';
|
||||
|
||||
describe('historyCollection', function () {
|
||||
let historyCollection, historyModel, onChangeSpy, onResetSpy;
|
||||
let historyCollection, historyModel, onAddSpy, onResetSpy;
|
||||
beforeEach(() => {
|
||||
historyModel = [{some: 'thing', someOther: ['array element']}];
|
||||
historyCollection = new HistoryCollection(historyModel);
|
||||
onChangeSpy = jasmine.createSpy('onChangeHandler');
|
||||
onAddSpy = jasmine.createSpy('onAddHandler');
|
||||
onResetSpy = jasmine.createSpy('onResetHandler');
|
||||
|
||||
historyCollection.onChange(onChangeSpy);
|
||||
historyCollection.onAdd(onAddSpy);
|
||||
historyCollection.onReset(onResetSpy);
|
||||
});
|
||||
|
||||
@ -36,12 +35,13 @@ describe('historyCollection', function () {
|
||||
|
||||
describe('add', function () {
|
||||
let expectedHistory;
|
||||
let newEntry = {some: 'new thing', someOther: ['value1', 'value2']};
|
||||
beforeEach(() => {
|
||||
historyCollection.add({some: 'new thing', someOther: ['value1', 'value2']});
|
||||
historyCollection.add(newEntry);
|
||||
|
||||
expectedHistory = [
|
||||
{some: 'thing', someOther: ['array element']},
|
||||
{some: 'new thing', someOther: ['value1', 'value2']},
|
||||
{some: 'thing', someOther: ['array element']},
|
||||
];
|
||||
});
|
||||
|
||||
@ -50,7 +50,7 @@ describe('historyCollection', function () {
|
||||
});
|
||||
|
||||
it('calls the onChange function', function () {
|
||||
expect(onChangeSpy).toHaveBeenCalledWith(expectedHistory);
|
||||
expect(onAddSpy).toHaveBeenCalledWith(newEntry);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -9,59 +9,38 @@
|
||||
|
||||
/* eslint-disable react/no-find-dom-node */
|
||||
|
||||
import jasmineEnzyme from 'jasmine-enzyme';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import QueryHistory from 'sources/sqleditor/history/query_history';
|
||||
import HistoryCollection from 'sources/sqleditor/history/history_collection';
|
||||
import clipboard from 'sources/selection/clipboard';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
|
||||
import QueryHistory from '../../../pgadmin/static/jsx/history/query_history';
|
||||
import QueryHistoryEntry from '../../../pgadmin/static/jsx/history/query_history_entry';
|
||||
import QueryHistoryEntryDateGroup from '../../../pgadmin/static/jsx/history/query_history_entry_date_group';
|
||||
import QueryHistoryEntries from '../../../pgadmin/static/jsx/history/query_history_entries';
|
||||
import QueryHistoryDetail from '../../../pgadmin/static/jsx/history/query_history_detail';
|
||||
import HistoryCollection from '../../../pgadmin/static/js/history/history_collection';
|
||||
import clipboard from '../../../pgadmin/static/js/selection/clipboard';
|
||||
|
||||
import {mount} from 'enzyme';
|
||||
import '../helper/enzyme.helper';
|
||||
|
||||
|
||||
describe('QueryHistory', () => {
|
||||
let historyCollection;
|
||||
let historyWrapper;
|
||||
let sqlEditorPref = {sql_font_size: '1em'};
|
||||
let sqlEditorPref = {sql_font_size: '1.5em'};
|
||||
let historyComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
jasmineEnzyme();
|
||||
historyWrapper = $('<div id="history_grid"></div>').appendTo('body');
|
||||
});
|
||||
|
||||
describe('when there is no history', () => {
|
||||
beforeEach(() => {
|
||||
historyCollection = new HistoryCollection([]);
|
||||
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}
|
||||
sqlEditorPref={sqlEditorPref}
|
||||
/>);
|
||||
});
|
||||
|
||||
describe('when we switch to the query history tab', () => {
|
||||
beforeEach(() => {
|
||||
historyWrapper.instance().refocus();
|
||||
spyOn(historyWrapper.instance(), 'retrieveSelectedQuery');
|
||||
});
|
||||
|
||||
it('does not try to focus on any element', () => {
|
||||
expect(historyWrapper.instance().retrieveSelectedQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
historyComponent = new QueryHistory(historyWrapper, historyCollection);
|
||||
historyComponent.render();
|
||||
});
|
||||
|
||||
it('has no entries', (done) => {
|
||||
let foundChildren = historyWrapper.find(QueryHistoryEntry);
|
||||
let foundChildren = historyWrapper.find('#query_list');
|
||||
expect(foundChildren.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
|
||||
it('nothing is displayed in the history details panel', (done) => {
|
||||
let foundChildren = historyWrapper.find(QueryHistoryDetail);
|
||||
expect(foundChildren.length).toBe(0);
|
||||
it('No history found is displayed', (done) => {
|
||||
expect(historyWrapper.find('.pg-panel-message').html()).toBe('No history found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -69,8 +48,6 @@ describe('QueryHistory', () => {
|
||||
describe('when there is history', () => {
|
||||
let queryEntries;
|
||||
let queryDetail;
|
||||
let isInvisibleSpy;
|
||||
let queryHistoryEntriesComponent;
|
||||
|
||||
describe('when two SQL queries were executed', () => {
|
||||
|
||||
@ -90,91 +67,38 @@ describe('QueryHistory', () => {
|
||||
total_time: '234 msec',
|
||||
message: 'something important ERROR: message from second sql query',
|
||||
}];
|
||||
|
||||
historyCollection = new HistoryCollection(historyObjects);
|
||||
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}
|
||||
sqlEditorPref={sqlEditorPref}
|
||||
/>);
|
||||
historyComponent = new QueryHistory(historyWrapper, historyCollection);
|
||||
historyComponent.render();
|
||||
|
||||
queryHistoryEntriesComponent = historyWrapper.find(QueryHistoryEntries);
|
||||
isInvisibleSpy = spyOn(queryHistoryEntriesComponent.instance(), 'isInvisible')
|
||||
.and.returnValue(false);
|
||||
|
||||
queryEntries = queryHistoryEntriesComponent.find(QueryHistoryEntry);
|
||||
queryDetail = historyWrapper.find(QueryHistoryDetail);
|
||||
queryEntries = historyWrapper.find('#query_list .list-item');
|
||||
queryDetail = historyWrapper.find('#query_detail');
|
||||
});
|
||||
|
||||
describe('the history entries panel', () => {
|
||||
it('has two query history entries', () => {
|
||||
it('has two query history entries', (done) => {
|
||||
expect(queryEntries.length).toBe(2);
|
||||
done();
|
||||
});
|
||||
|
||||
it('displays the query history entries in order', () => {
|
||||
expect(queryEntries.at(0).text()).toContain('first sql statement');
|
||||
expect(queryEntries.at(1).text()).toContain('second sql statement');
|
||||
expect($(queryEntries[0]).text()).toContain('first sql statement');
|
||||
expect($(queryEntries[1]).text()).toContain('second sql statement');
|
||||
});
|
||||
|
||||
it('displays the formatted timestamp of the queries in chronological order by most recent first', () => {
|
||||
expect(queryEntries.at(0).find('.timestamp').text()).toBe('14:03:15');
|
||||
expect(queryEntries.at(1).find('.timestamp').text()).toBe('01:33:05');
|
||||
expect($(queryEntries[0]).find('.timestamp').text()).toBe('14:03:15');
|
||||
expect($(queryEntries[1]).find('.timestamp').text()).toBe('01:33:05');
|
||||
});
|
||||
|
||||
it('renders the most recent query as selected', () => {
|
||||
expect(queryEntries.at(0).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(0).render().hasClass('selected')).toBeTruthy();
|
||||
expect($(queryEntries[0]).hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders the older query as not selected', () => {
|
||||
expect(queryEntries.at(1).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(1).render().hasClass('selected')).toBeFalsy();
|
||||
expect(queryEntries.at(1).render().hasClass('error')).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('when the selected query is the most recent', () => {
|
||||
describe('when we press arrow down', () => {
|
||||
beforeEach(() => {
|
||||
pressArrowDownKey(queryHistoryEntriesComponent.find('.list-item').at(0));
|
||||
});
|
||||
|
||||
it('should select the next query', () => {
|
||||
expect(queryEntries.at(1).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(1).render().hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display the corresponding detail on the right pane', () => {
|
||||
expect(queryDetail.at(0).render().text()).toContain('message from second sql query');
|
||||
});
|
||||
|
||||
describe('when arrow down pressed again', () => {
|
||||
it('should not change the selected query', () => {
|
||||
pressArrowDownKey(queryHistoryEntriesComponent.find('.list-item').at(0));
|
||||
|
||||
expect(queryEntries.at(1).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(1).render().hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when arrow up is pressed', () => {
|
||||
beforeEach(() => {
|
||||
pressArrowUpKey(queryHistoryEntriesComponent.find('.list-item').at(0));
|
||||
});
|
||||
|
||||
it('should select the most recent query', () => {
|
||||
expect(queryEntries.at(0).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(0).render().hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when arrow up is pressed', () => {
|
||||
beforeEach(() => {
|
||||
pressArrowUpKey(queryHistoryEntriesComponent.find('.list-item').at(0));
|
||||
});
|
||||
|
||||
it('should not change the selected query', () => {
|
||||
expect(queryEntries.at(0).getElements().length).toBe(1);
|
||||
expect(queryEntries.at(0).render().hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
expect($(queryEntries[1]).hasClass('selected')).toBeFalsy();
|
||||
expect($(queryEntries[1]).find('.entry').hasClass('error')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -185,29 +109,34 @@ describe('QueryHistory', () => {
|
||||
copyAllButton = () => queryDetail.find('#history-detail-query > button');
|
||||
});
|
||||
|
||||
it('should change preferences', ()=>{
|
||||
historyComponent.setEditorPref(sqlEditorPref);
|
||||
expect(queryDetail.find('#history-detail-query .CodeMirror').attr('style')).toBe('font-size: 1.5em;');
|
||||
});
|
||||
|
||||
it('displays the formatted timestamp', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('6-3-17 14:03:15Date');
|
||||
expect(queryDetail.text()).toContain('6-3-17 14:03:15');
|
||||
});
|
||||
|
||||
it('displays the number of rows affected', () => {
|
||||
if (/PhantomJS/.test(window.navigator.userAgent)) {
|
||||
expect(queryDetail.at(0).text()).toContain('12345Rows Affected');
|
||||
expect(queryDetail.text()).toContain('12345');
|
||||
} else {
|
||||
expect(queryDetail.at(0).text()).toContain('12,345Rows Affected');
|
||||
expect(queryDetail.text()).toContain('12,345');
|
||||
}
|
||||
});
|
||||
|
||||
it('displays the total time', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('14 msecDuration');
|
||||
expect(queryDetail.text()).toContain('14 msec');
|
||||
});
|
||||
|
||||
it('displays the full message', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('message from first sql query');
|
||||
expect(queryDetail.text()).toContain('message from first sql query');
|
||||
});
|
||||
|
||||
it('displays first query SQL', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(queryDetail.at(0).text()).toContain('first sql statement');
|
||||
expect(queryDetail.text()).toContain('first sql statement');
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
@ -215,7 +144,7 @@ describe('QueryHistory', () => {
|
||||
describe('when the "Copy All" button is clicked', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(clipboard, 'copyTextToClipboard');
|
||||
copyAllButton().simulate('click');
|
||||
copyAllButton().trigger('click');
|
||||
});
|
||||
|
||||
it('copies the query to the clipboard', () => {
|
||||
@ -242,7 +171,7 @@ describe('QueryHistory', () => {
|
||||
|
||||
describe('when the copy button is clicked', () => {
|
||||
beforeEach(() => {
|
||||
copyAllButton().simulate('click');
|
||||
copyAllButton().trigger('click');
|
||||
});
|
||||
|
||||
describe('before 1.5 seconds', () => {
|
||||
@ -255,7 +184,7 @@ describe('QueryHistory', () => {
|
||||
});
|
||||
|
||||
it('should have the class \'was-copied\'', () => {
|
||||
expect(copyAllButton().render().hasClass('was-copied')).toBe(true);
|
||||
expect(copyAllButton().hasClass('was-copied')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -272,7 +201,7 @@ describe('QueryHistory', () => {
|
||||
describe('when is clicked again after 1s', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.clock().tick(1000);
|
||||
copyAllButton().simulate('click');
|
||||
copyAllButton().trigger('click');
|
||||
|
||||
});
|
||||
|
||||
@ -286,7 +215,7 @@ describe('QueryHistory', () => {
|
||||
});
|
||||
|
||||
it('should have the class \'was-copied\'', () => {
|
||||
expect(copyAllButton().render().hasClass('was-copied')).toBe(true);
|
||||
expect(copyAllButton().hasClass('was-copied')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@ -307,76 +236,39 @@ describe('QueryHistory', () => {
|
||||
let failedEntry;
|
||||
|
||||
beforeEach(() => {
|
||||
failedEntry = queryEntries.at(1);
|
||||
failedEntry.simulate('click');
|
||||
failedEntry = $(queryEntries[1]);
|
||||
failedEntry.trigger('click');
|
||||
});
|
||||
|
||||
it('displays the error message on top of the details pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('Error Message message from second sql query');
|
||||
expect(queryDetail.text()).toContain('Error Message message from second sql query');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('when the older query is clicked on', () => {
|
||||
let firstEntry, secondEntry;
|
||||
|
||||
beforeEach(() => {
|
||||
firstEntry = queryEntries.at(0);
|
||||
secondEntry = queryEntries.at(1);
|
||||
secondEntry.simulate('click');
|
||||
firstEntry = $(queryEntries[0]);
|
||||
secondEntry = $(queryEntries[1]);
|
||||
secondEntry.trigger('click');
|
||||
});
|
||||
|
||||
it('displays the query in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('second sql statement');
|
||||
expect(queryDetail.text()).toContain('second sql statement');
|
||||
});
|
||||
|
||||
it('deselects the first history entry', () => {
|
||||
expect(firstEntry.getElements().length).toBe(1);
|
||||
expect(firstEntry.hasClass('selected')).toBeFalsy();
|
||||
|
||||
});
|
||||
|
||||
it('selects the second history entry', () => {
|
||||
expect(secondEntry.getElements().length).toBe(1);
|
||||
expect(secondEntry.render().hasClass('selected')).toBeTruthy();
|
||||
expect(secondEntry.hasClass('selected')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the first query is outside the visible area', () => {
|
||||
beforeEach(() => {
|
||||
isInvisibleSpy.and.callFake((element) => {
|
||||
return element.textContent.contains('first sql statement');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the first query is the selected query', () => {
|
||||
describe('when refocus function is called', () => {
|
||||
let selectedListItem;
|
||||
|
||||
beforeEach(() => {
|
||||
selectedListItem = ReactDOM.findDOMNode(historyWrapper.instance())
|
||||
.getElementsByClassName('selected')[0].parentElement;
|
||||
|
||||
spyOn(selectedListItem, 'focus');
|
||||
|
||||
jasmine.clock().install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
|
||||
it('the first query scrolls into view', () => {
|
||||
historyWrapper.instance().refocus();
|
||||
expect(selectedListItem.focus).toHaveBeenCalledTimes(0);
|
||||
jasmine.clock().tick(1);
|
||||
expect(selectedListItem.focus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when a third SQL query is executed', () => {
|
||||
beforeEach(() => {
|
||||
historyCollection.add({
|
||||
@ -388,11 +280,11 @@ describe('QueryHistory', () => {
|
||||
message: 'pretext ERROR: third sql message',
|
||||
});
|
||||
|
||||
queryEntries = historyWrapper.find(QueryHistoryEntry);
|
||||
queryEntries = historyWrapper.find('#query_list .list-item');
|
||||
});
|
||||
|
||||
it('displays third query SQL in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('third sql statement');
|
||||
expect(queryDetail.text()).toContain('third sql statement');
|
||||
});
|
||||
});
|
||||
|
||||
@ -407,33 +299,13 @@ describe('QueryHistory', () => {
|
||||
message: 'ERROR: unexpected error from fourth sql message',
|
||||
});
|
||||
|
||||
queryEntries = historyWrapper.find(QueryHistoryEntry);
|
||||
queryEntries = historyWrapper.find('#query_list .list-item');
|
||||
});
|
||||
|
||||
it('displays fourth query SQL in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('Error Message unexpected error from fourth sql message');
|
||||
expect(queryDetail.text()).toContain('Error Message unexpected error from fourth sql message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a fifth SQL query is executed', () => {
|
||||
beforeEach(() => {
|
||||
historyCollection.add({
|
||||
query: 'fifth sql statement',
|
||||
start_time: new Date(2017, 12, 12, 1, 33, 5, 99),
|
||||
status: false,
|
||||
row_affected: 0,
|
||||
total_time: '26 msec',
|
||||
message: 'unknown error',
|
||||
});
|
||||
|
||||
queryEntries = historyWrapper.find(QueryHistoryEntry);
|
||||
});
|
||||
|
||||
it('displays fourth query SQL in the right pane', () => {
|
||||
expect(queryDetail.at(0).text()).toContain('Error Message unknown error');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when several days of queries were executed', () => {
|
||||
@ -482,16 +354,11 @@ describe('QueryHistory', () => {
|
||||
}];
|
||||
historyCollection = new HistoryCollection(historyObjects);
|
||||
|
||||
historyWrapper = mount(<QueryHistory historyCollection={historyCollection}
|
||||
sqlEditorPref={sqlEditorPref}
|
||||
/>);
|
||||
historyComponent = new QueryHistory(historyWrapper, historyCollection);
|
||||
historyComponent.render();
|
||||
|
||||
const queryHistoryEntriesComponent = historyWrapper.find(QueryHistoryEntries);
|
||||
isInvisibleSpy = spyOn(queryHistoryEntriesComponent.instance(), 'isInvisible')
|
||||
.and.returnValue(false);
|
||||
|
||||
queryEntries = queryHistoryEntriesComponent.find(QueryHistoryEntry);
|
||||
queryEntryDateGroups = queryHistoryEntriesComponent.find(QueryHistoryEntryDateGroup);
|
||||
queryEntries = historyWrapper.find('#query_list .list-item');
|
||||
queryEntryDateGroups = historyWrapper.find('#query_list .query-group');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -504,23 +371,11 @@ describe('QueryHistory', () => {
|
||||
});
|
||||
|
||||
it('has title above', () => {
|
||||
expect(queryEntryDateGroups.at(0).text()).toBe('Today - Jul 01 2017');
|
||||
expect(queryEntryDateGroups.at(1).text()).toBe('Yesterday - Jun 30 2017');
|
||||
expect(queryEntryDateGroups.at(2).text()).toBe('Jun 28 2017');
|
||||
expect($(queryEntryDateGroups[0]).find('.date-label').text()).toBe('Today - Jul 01 2017');
|
||||
expect($(queryEntryDateGroups[1]).find('.date-label').text()).toBe('Yesterday - Jun 30 2017');
|
||||
expect($(queryEntryDateGroups[2]).find('.date-label').text()).toBe('Jun 28 2017');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function pressArrowUpKey(node) {
|
||||
pressKey(node, 38);
|
||||
}
|
||||
|
||||
function pressArrowDownKey(node) {
|
||||
pressKey(node, 40);
|
||||
}
|
||||
|
||||
function pressKey(node, keyCode) {
|
||||
node.simulate('keyDown', {keyCode: keyCode});
|
||||
}
|
||||
});
|
@ -201,12 +201,12 @@ module.exports = {
|
||||
// It solves number of problems
|
||||
// Ref: http:/github.com/webpack-contrib/imports-loader/
|
||||
rules: [{
|
||||
test: /\.jsx?$/,
|
||||
test: /\.js$/,
|
||||
exclude: [/node_modules/, /vendor/],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['es2015', 'react'],
|
||||
presets: ['es2015'],
|
||||
plugins: ['transform-object-rest-spread'],
|
||||
},
|
||||
},
|
||||
@ -391,7 +391,7 @@ module.exports = {
|
||||
resolve: {
|
||||
alias: webpackShimConfig.resolveAlias,
|
||||
modules: ['node_modules', '.'],
|
||||
extensions: ['.js', '.jsx'],
|
||||
extensions: ['.js'],
|
||||
unsafeCache: true,
|
||||
},
|
||||
// Watch mode Configuration: After initial build, webpack will watch for
|
||||
|
@ -134,7 +134,6 @@ var webpackShimConfig = {
|
||||
'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
|
||||
'bundled_browser': path.join(__dirname, './pgadmin/static/bundle/browser'),
|
||||
'sources': path.join(__dirname, './pgadmin/static/js'),
|
||||
'sourcesjsx': path.join(__dirname, './pgadmin/static/jsx'),
|
||||
'pgadmin': path.join(__dirname, './pgadmin/static/js/pgadmin'),
|
||||
'translations': path.join(__dirname, './pgadmin/tools/templates/js/translations'),
|
||||
'sources/gettext': path.join(__dirname, './pgadmin/static/js/gettext'),
|
||||
|
@ -31,12 +31,13 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.jsx?$/,
|
||||
test: /\.js$/,
|
||||
exclude: [/node_modules/, /vendor/],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['es2015', 'react', 'airbnb'],
|
||||
presets: ['es2015'],
|
||||
plugins: ['transform-object-rest-spread'],
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -67,7 +68,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
extensions: ['.js'],
|
||||
alias: {
|
||||
'top': path.join(__dirname, './pgadmin'),
|
||||
'jquery': path.join(__dirname, './node_modules/jquery/dist/jquery'),
|
||||
@ -100,6 +101,7 @@ module.exports = {
|
||||
'pgbrowser': path.resolve(__dirname, 'regression/javascript/fake_browser'),
|
||||
'pgadmin.schema.dir': path.resolve(__dirname, 'pgadmin/browser/server_groups/servers/databases/schemas/static/js'),
|
||||
'pgadmin.browser.preferences': path.join(__dirname, './pgadmin/browser/static/js/preferences'),
|
||||
'bundled_codemirror': path.join(__dirname, './pgadmin/static/bundle/codemirror'),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
2187
web/yarn.lock
2187
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user