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:
committed by
Dave Page
parent
d7bf6ec69f
commit
4b895941b3
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user