HM-71. Доработки таблицы логов. Универсальной таблице добавлена возможность переопределять рендер строк и заголовков (#31)

This commit is contained in:
Nikolay
2020-07-25 14:23:52 +03:00
committed by GitHub
parent c93a1b8ddb
commit c425ffea45
19 changed files with 231 additions and 63 deletions

View File

@ -57,6 +57,7 @@
"dependencies": {
"axios": "^0.19.2",
"bootstrap": "^5.0.0-alpha1",
"moment": "^2.27.0",
"popper.js": "^1.16.1",
"query-string": "^6.13.1",
"uuid": "^8.2.0"

View File

@ -0,0 +1,15 @@
import Table from '../table';
import {LOG_COLS, LOG_TYPE} from '../../consts';
import ClientLogsTableRow from './ClientLogsTableRow';
class ClientLogsTable extends Table {
constructor () {
super(null, LOG_COLS[LOG_TYPE.CLIENT]);
}
renderRow = (parentNode, cols, row) => {
return new ClientLogsTableRow(parentNode, cols, row);
}
}
export default ClientLogsTable;

View File

@ -0,0 +1,12 @@
import Component from '../component';
import TableCellOverflow from '../table-cell-overflow';
class ClientLogsTableRow extends Component {
constructor (parentNode, cols, row) {
super(null, parentNode);
this.cols = cols.map((col) => new TableCellOverflow(this.mainNode, row[col.id]));
}
}
export default ClientLogsTableRow;

View File

@ -0,0 +1,3 @@
import ClientLogsTable from './ClientLogsTable';
export default ClientLogsTable;

View File

@ -26,15 +26,25 @@ class Component extends EmitService {
constructor (mainNodeSelector, parentNode) {
super();
const content = document.querySelector(mainNodeSelector).content;
if (content.children.length > 1) {
const message = '<template> должен содержать только один элемент children';
throw new Error(message);
if (!mainNodeSelector && !parentNode) {
throw new Error('Компонент должен содержать хотябы селектор или родительский Node');
}
this.mainNode = content.firstElementChild.cloneNode(true);
if (parentNode) {
parentNode.appendChild(this.mainNode);
if (mainNodeSelector) {
const content = document.querySelector(mainNodeSelector).content;
if (content.children.length > 1) {
const message = '<template> должен содержать только один элемент children';
throw new Error(message);
}
this.mainNode = content.firstElementChild.cloneNode(true);
if (parentNode) {
parentNode.appendChild(this.mainNode);
}
} else {
this.mainNode = parentNode;
}
this._listeners = [];
this._events = {};
this._isAlive = true;

View File

@ -1,12 +1,13 @@
import Component from '../component/index';
import Table from '../table';
import storageLogsApi from '../../api/StorageLogsAPI';
import Pagination from '../pagination';
import LogsFilters from '../logs-filters';
import routeService from '../../services/RouteService';
import {prepareToNumber} from '../../utils/urlUtils';
import {LOG_TYPE, LOG_COLS, EVENTS} from '../../consts';
import {createElement, markText} from '../../utils/elementUtils';
import {LOG_TYPE, EVENTS} from '../../consts';
import {createElement, markText, prepareServerDate, prepareObjectToString} from '../../utils/elementUtils';
import ServerLogsTable from '../server-logs-table';
import ClientLogsTable from '../client-logs-table';
const ELEMENTS_ON_PAGE = 15;
@ -30,8 +31,8 @@ class LogsPage extends Component {
this.filters = new LogsFilters(this.header);
this.tables = {
[LOG_TYPE.SERVER]: new Table(null, LOG_COLS[LOG_TYPE.SERVER]),
[LOG_TYPE.CLIENT]: new Table(null, LOG_COLS[LOG_TYPE.CLIENT]),
[LOG_TYPE.SERVER]: new ServerLogsTable(),
[LOG_TYPE.CLIENT]: new ClientLogsTable(),
};
this.pagination = new Pagination(this.footer);
@ -86,6 +87,23 @@ class LogsPage extends Component {
return filteredRows.slice(start, end + 1);
}
prepareCells = (columns, prepareFunction) => {
return Object.entries(columns).reduce((memo, [colName, colValue]) => ({
...memo,
...(colValue ? {
[colName]: prepareFunction(colValue),
} : {}),
}), {});
}
prepareRows = (rows) => {
return rows.map(({startTime, endTime, date, request, response, ...omitProps}) => ({
...omitProps,
...this.prepareCells({startTime, endTime, date}, prepareServerDate),
...this.prepareCells({request, response}, prepareObjectToString),
}));
}
renderTable = () => {
this.render(() => {
const {query} = routeService.getUrlData();
@ -95,7 +113,8 @@ class LogsPage extends Component {
this.body.appendChild(this.table.mainNode);
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
const rows = this.cutPagginationPage(filteredRows, query.pageNumber);
this.table.render(rows);
this.table.render(this.prepareRows(rows));
});
}
}

View File

@ -0,0 +1,16 @@
import Table from '../table';
import {LOG_TYPE, LOG_COLS} from '../../consts';
import ServerLogsTableRow from './ServerLogsTableRow';
class ServerLogsTable extends Table {
constructor () {
super(null, LOG_COLS[LOG_TYPE.SERVER]);
}
renderRow = (parentNode, cols, row) => {
return new ServerLogsTableRow(parentNode, cols, row);
}
}
export default ServerLogsTable;

View File

@ -0,0 +1,12 @@
import Component from '../component';
import TableCellOverflow from '../table-cell-overflow';
class ServerLogsTableRow extends Component {
constructor (parentNode, cols, row) {
super(null, parentNode);
this.cols = cols.map((col) => new TableCellOverflow(this.mainNode, row[col.id]));
}
}
export default ServerLogsTableRow;

View File

@ -0,0 +1,3 @@
import ServerLogsTable from './ServerLogsTable';
export default ServerLogsTable;

View File

@ -0,0 +1,14 @@
.TableCellOverflow__cellWrapper {
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.TableCellOverflow__cell {
max-width: 100%;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -0,0 +1,33 @@
import Component from '../component';
import {createElement} from '../../utils/elementUtils';
import './TableCellOverflow.css';
class TableCellOverflow extends Component {
constructor (parentNode, text) {
super(null, parentNode);
const cell = createElement({
tagName: 'td',
parentNode: this.mainNode,
});
const div = createElement({
tagName: 'div',
parentNode: cell,
options: {
className: 'TableCellOverflow__cellWrapper'
}
});
const span = createElement({
tagName: 'span',
parentNode: div,
options: {
className: 'TableCellOverflow__cell',
},
});
span.innerHTML = text;
}
}
export default TableCellOverflow;

View File

@ -0,0 +1,3 @@
import TableCellOverflow from './TableCellOverflow';
export default TableCellOverflow;

View File

@ -5,8 +5,8 @@ class HeaderCol extends Component {
super('#uni-table-th', parentNode);
this.id = col.id;
this.mainNode.textContent = col.label;
this.mainNode.style.minWidth = col.width ?? '';
this.mainNode.textContent = col.label;
}
}

View File

@ -0,0 +1,18 @@
import Component from '../component';
import {createElement} from '../../utils/elementUtils';
class RowCol extends Component {
constructor (parentNode, text) {
super(null, parentNode);
createElement({
tagName: 'td',
parentNode: this.mainNode,
options: {
innerHTML: text,
},
});
}
}
export default RowCol;

View File

@ -1,8 +1,8 @@
import Component from '../component/index';
import HeaderCol from './HeaderCol';
import TableRow from './TableRow';
const ROW_CLICK = 'ROW_CLICK';
import TableRowWrapper from './TableRowWrapper';
import {EVENTS} from '../../consts';
/**
* @interface Col
@ -33,6 +33,8 @@ class Table extends Component {
constructor (parentNode, cols) {
super('#uni-table', parentNode);
this.mainNode.classList.add('Table');
this.theadTr = this.mainNode.querySelector('thead tr');
this.tbody = this.mainNode.querySelector('tbody');
@ -40,6 +42,14 @@ class Table extends Component {
this.renderHeader();
}
renderHeaderCol = (col) => {
return new HeaderCol(this.theadTr, col);
}
renderRow = (parentNode, cols, row) => {
return new TableRow(parentNode, cols, row);
}
/**
* Метод нужен для отрисовки шапки таблицы, вызывается конструктором самостоятельно
*/
@ -49,7 +59,8 @@ class Table extends Component {
col.destroy();
});
}
this.headerCols = this.cols.map((col) => new HeaderCol(this.theadTr, col));
this.headerCols = this.cols.map(this.renderHeaderCol);
}
/**
@ -58,32 +69,24 @@ class Table extends Component {
*/
render = (rows) => {
if (this.rows) {
this.rows.forEach((row) => {
row.destroy();
this.rows.forEach(({tr, rowComponent}) => {
tr.destroy();
rowComponent.destroy();
});
}
this.rows = rows.map((row) => {
const rowComponent = new TableRow(this.tbody, this.cols, row);
rowComponent.onClick((event) => {
this.next(ROW_CLICK, event, row);
const tr = new TableRowWrapper(this.tbody, row);
this.addSubscribe(tr, EVENTS.ROW_CLICK, (event) => {
this.next(EVENTS.ROW_CLICK, event, row);
});
return rowComponent;
const rowComponent = this.renderRow(tr.mainNode, this.cols, row);
return {
tr,
rowComponent,
};
});
}
/**
* Метод для подписки на клики по строчкам, передает event и данные строчки
* @param {Function} listener - слушатель
*
* @example
* table.onRowClick((event, row) => {
* // Ваш код
* });
*/
onRowClick = (listener) => {
this.subscribe(ROW_CLICK, listener);
}
}
export default Table;

View File

@ -1,30 +1,11 @@
import Component from '../component/index';
import {createElement} from '../../utils/elementUtils';
const CLICK = 'click';
import RowCol from './RowCol';
class TableRow extends Component {
constructor (parentNode, cols, row) {
super('#uni-table-row', parentNode);
super(null, parentNode);
cols.forEach((col) => {
const cell = createElement({
tagName: 'td',
parentNode: this.mainNode,
options: {
className: 'text-break'
},
});
cell.innerHTML = row[col.id];
});
this.mainNode.addEventListener(CLICK, (event) => {
this.next(CLICK, event);
});
}
onClick = (listener) => {
this.subscribe(CLICK, listener);
this.cols = cols.map((col) => new RowCol(this.mainNode, row[col.id]));
}
}

View File

@ -0,0 +1,14 @@
import Component from '../component';
import {EVENTS} from '../../consts';
class TableRowWrapper extends Component {
constructor (parentNode) {
super('#uni-table-row', parentNode);
this.addEventListener(this.mainNode, 'click', () => {
this.next(EVENTS.ROW_CLICK);
});
}
}
export default TableRowWrapper;

View File

@ -18,11 +18,11 @@ export const SERVER_COLS = [
export const CLIENT_COLS = [
{id: '_id', label: 'id', width: '240px'},
{id: 'type', label: 'Результат запроса'},
{id: 'type', label: 'Результат', width: '100px'},
{id: 'request', label: 'Запрос клиента', width: '240px'},
{id: 'response', label: 'Ответ сервера', width: '240px'},
{id: 'startTime', label: 'Начало запроса', width: '150px'},
{id: 'endTime', label: 'Окончание запроса', width: '150px'},
{id: 'response', label: 'Ответ сервера'},
{id: 'startTime', label: 'Начало', width: '150px'},
{id: 'endTime', label: 'Окончание', width: '150px'},
];
export const LOG_COLS = {
@ -32,5 +32,6 @@ export const LOG_COLS = {
export const EVENTS = {
ROUTE_CHANGE: 'routeChange',
ROUTE_SEARCH_CHANGE: 'routeSearchChange'
ROUTE_SEARCH_CHANGE: 'routeSearchChange',
ROW_CLICK: 'rowClick',
};

View File

@ -1,3 +1,5 @@
import moment from 'moment';
/**
* @interface CreateElementProps
* @type {Object}
@ -46,3 +48,11 @@ export const markText = (searchMessage, text, styleClassName = 'text-warning bg-
`<span class="${styleClassName}">${match}</span>`
));
};
export const prepareServerDate = (stringDate) => {
return moment(stringDate).format('DD/MM/YYYY hh:mm:ss');
};
export const prepareObjectToString = (object) => {
return JSON.stringify(object);
};