refs #5517 Added: Tests, filter panel, model and props titles
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Juan Ferrer 2023-04-20 19:46:57 +02:00
parent 6f1744baff
commit 9694a35343
10 changed files with 329 additions and 18 deletions

View File

@ -0,0 +1,79 @@
import './index';
describe('Salix Component vnLog', () => {
let controller;
let $scope;
let $element;
let el;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<json-value></json-value>');
controller = $componentController('vnJsonValue', {$element, $scope});
el = controller.element;
}));
describe('set value()', () => {
it('should display null symbol when value is null equivalent', () => {
controller.value = null;
expect(el.textContent).toEqual('∅');
expect(el.className).toContain('json-null');
});
it('should display ballot when value is false', () => {
controller.value = false;
expect(el.textContent).toEqual('✗');
expect(el.className).toContain('json-false');
});
it('should display check when value is true', () => {
controller.value = true;
expect(el.textContent).toEqual('✓');
expect(el.className).toContain('json-true');
});
it('should display string when value is an string', () => {
controller.value = 'Foo';
expect(el.textContent).toEqual('Foo');
expect(el.className).toContain('json-string');
});
it('should display only date when value is date with time set to zero', () => {
const date = Date.vnNew();
date.setHours(0, 0, 0, 0);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001');
expect(el.className).toContain('json-object');
});
it('should display full date without time when value is date with time', () => {
const date = Date.vnNew();
date.setHours(15, 45);
controller.value = date;
expect(el.textContent).toEqual('01/01/2001 15:45:00');
expect(el.className).toContain('json-object');
});
it('should display object when value is an object', () => {
controller.value = {foo: 'bar'};
expect(el.textContent).toEqual('[object Object]');
expect(el.className).toContain('json-object');
});
it('should display number when value is a number', () => {
controller.value = 2050;
expect(el.textContent).toEqual('2050');
expect(el.className).toContain('json-number');
});
});
});

View File

@ -5,8 +5,7 @@ vn-json-value {
color: #d172cc; color: #d172cc;
} }
&.json-object { &.json-object {
/*color: #d1a572;*/ color: #d1a572;
color: #d172cc;
} }
&.json-number { &.json-number {
color: #85d0ff; color: #85d0ff;

View File

@ -2,7 +2,6 @@
$font-size: 11pt; $font-size: 11pt;
$menu-width: 256px; $menu-width: 256px;
$right-menu-width: 318px;
$topbar-height: 56px; $topbar-height: 56px;
$mobile-width: 800px; $mobile-width: 800px;
$float-spacing: 20px; $float-spacing: 20px;

View File

@ -88,13 +88,13 @@ vn-layout {
} }
&.right-menu { &.right-menu {
& > vn-topbar > .end { & > vn-topbar > .end {
width: 80px + $right-menu-width; width: 80px + $menu-width;
} }
& > .main-view { & > .main-view {
padding-right: $right-menu-width; padding-right: $menu-width;
} }
[fixed-bottom-right] { [fixed-bottom-right] {
right: $right-menu-width; right: $menu-width;
} }
} }
& > .main-view { & > .main-view {

View File

@ -6,6 +6,7 @@
where="{changedModel: $ctrl.changedModel, where="{changedModel: $ctrl.changedModel,
changedModelId: $ctrl.changedModelId}" changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs" data="$ctrl.logs"
order="creationDate DESC, id DESC"
limit="20" limit="20"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
@ -38,13 +39,16 @@
</span> </span>
</td> </td>
<td title="{{::log.changedModelValue}}"> <td title="{{::log.changedModelValue}}">
<span class="model-name" ng-if="::$ctrl.showModelName"> <span class="model-name"
{{::log.changedModel}} ng-if="::$ctrl.showModelName"
title="{{::log.changedModel}}">
{{::log.changedModelI18n}}
</span> </span>
<span class="model-value"> <span class="model-value">
{{::log.changedModelValue}} {{::log.changedModelValue}}
</span> </span>
<span class="model-id"> <span class="model-id"
ng-if="::log.changedModelId">
#{{::log.changedModelId}} #{{::log.changedModelId}}
</span> </span>
</td> </td>
@ -73,13 +77,19 @@
class="attributes"> class="attributes">
<span ng-if="!log.expand" ng-repeat="prop in ::log.props" <span ng-if="!log.expand" ng-repeat="prop in ::log.props"
class="basic-json"> class="basic-json">
<span class="json-field">{{::prop.name}}:</span> <span class="json-field"
title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span> <vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value><span ng-if="::!$last">,</span>
</span> </span>
<div ng-if="log.expand" <div ng-if="log.expand"
class="expanded-json"> class="expanded-json">
<div ng-repeat="prop in ::log.props"> <div ng-repeat="prop in ::log.props">
<span class="json-field">{{::prop.name}}:</span> <span class="json-field"
title="{{::prop.name}}">
{{::prop.nameI18n}}:
</span>
<vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value> <vn-json-value value="::$ctrl.mainVal(prop, log.action)"></vn-json-value>
<span ng-if="::log.action == 'update'"> <span ng-if="::log.action == 'update'">
<vn-json-value value="::prop.old"></vn-json-value> <vn-json-value value="::prop.old"></vn-json-value>
@ -105,5 +115,72 @@
<vn-pagination model="model"></vn-pagination> <vn-pagination model="model"></vn-pagination>
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>
<vn-side-menu side="right">
<form vn-vertical
ng-submit="$ctrl.applyFilter(filter)"
ng-model-options="{updateOn: 'change blur'}"
class="vn-pa-md filter">
<vn-textfield
label="Model"
ng-model="filter.changedModel">
</vn-textfield>
<vn-textfield
label="Id"
ng-model="filter.changedModelId">
</vn-textfield>
<vn-autocomplete
label="User"
ng-model="filter.userFk"
value-field="id"
show-field="nickname"
fields="['id', 'name', 'nickname']"
search-function="$ctrl.searchUser($search)"
url="VnUsers">
<tpl-item>
<div>{{nickname}}</div>
<div class="text-secondary text-caption">{{name}}</div>
</tpl-item>
</vn-autocomplete>
<div vn-vertical>
<vn-check
label="Creates"
val="insert"
ng-model="filter.actions.insert">
</vn-check>
<vn-check
label="Updates"
val="update"
ng-model="filter.actions.update">
</vn-check>
<vn-check
label="Deletes"
val="delete"
ng-model="filter.actions.delete">
</vn-check>
<vn-check
label="Views"
val="select"
ng-model="filter.actions.select">
</vn-check>
</div>
<vn-date-picker
label="From"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="filter.to">
</vn-date-picker>
<vn-button-bar vn-vertical class="vn-mt-sm">
<vn-submit label="Buscar"></vn-submit>
<vn-button
label="Reset"
class="flat"
ng-click="$ctrl.removeFilter()"
ng-if="model.userFilter">
</vn-button>
</vn-button-bar>
</form>
</vn-side-menu>
<vn-worker-descriptor-popover vn-id="workerDescriptor"> <vn-worker-descriptor-popover vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>

View File

@ -19,6 +19,11 @@ export default class Controller extends Section {
delete: 'alert', delete: 'alert',
select: 'notice' select: 'notice'
}; };
$.filter = {};
$.$watch('filter.actions', () => this.applyFilter(), true);
$.$watch('filter.from', () => $.filter.to = $.filter.from);
this.filter = { this.filter = {
include: [{ include: [{
relation: 'user', relation: 'user',
@ -33,6 +38,7 @@ export default class Controller extends Section {
}, },
}], }],
}; };
this.dateFilter = this.$filter('date'); this.dateFilter = this.$filter('date');
this.lang = this.$translate.use(); this.lang = this.$translate.use();
this.today = Date.vnNew(); this.today = Date.vnNew();
@ -52,7 +58,7 @@ export default class Controller extends Section {
const oldValues = log.oldInstance || empty; const oldValues = log.oldInstance || empty;
const newValues = log.newInstance || empty; const newValues = log.newInstance || empty;
const locale = validations[log.changedModel]?.locale || empty; const locale = validations[log.changedModel]?.locale || empty;
log.changedModel = locale.name ? locale.name : log.changedModel log.changedModelI18n = locale.name || log.changedModel;
let props = Object.keys(oldValues).concat(Object.keys(newValues)); let props = Object.keys(oldValues).concat(Object.keys(newValues));
props = [...new Set(props)]; props = [...new Set(props)];
@ -60,9 +66,10 @@ export default class Controller extends Section {
log.props = []; log.props = [];
for (const prop of props) { for (const prop of props) {
log.props.push({ log.props.push({
name: locale.columns?.[prop] || prop, name: prop,
old: this.castValue(oldValues[prop]), nameI18n: locale.columns?.[prop] || prop,
new: this.castValue(newValues[prop]) old: this.castJsonValue(oldValues[prop]),
new: this.castJsonValue(newValues[prop])
}); });
} }
} }
@ -72,7 +79,7 @@ export default class Controller extends Section {
return !(this.changedModel && this.changedModelId); return !(this.changedModel && this.changedModelId);
} }
castValue(value) { castJsonValue(value) {
return typeof value === 'string' && validDate.test(value) return typeof value === 'string' && validDate.test(value)
? new Date(value) ? new Date(value)
: value; : value;
@ -88,6 +95,7 @@ export default class Controller extends Section {
} }
relativeDate(dateVal) { relativeDate(dateVal) {
if (dateVal == null) return '';
const date = new Date(dateVal); const date = new Date(dateVal);
const dateZeroTime = new Date(dateVal); const dateZeroTime = new Date(dateVal);
dateZeroTime.setHours(0, 0, 0, 0); dateZeroTime.setHours(0, 0, 0, 0);
@ -112,6 +120,54 @@ export default class Controller extends Section {
if (!workerId) return; if (!workerId) return;
this.$.workerDescriptor.show(event.target, workerId); this.$.workerDescriptor.show(event.target, workerId);
} }
applyFilter() {
const filter = this.$.filter;
function getParam(prop, value) {
if (value == null || value == '') return null;
switch (prop) {
case 'actions':
const inq = [];
for (const action in value) {
if (value[action])
inq.push(action);
}
return inq.length ? {action: {inq}} : null;
case 'from':
return {creationDate: {gte: value}};
case 'to':
const to = new Date(value);
to.setHours(23, 59, 59, 999);
return {creationDate: {lte: to}};
default:
return {[prop]: value};
}
}
const and = [];
for (const prop in filter) {
const param = getParam(prop, filter[prop]);
if (param) and.push(param);
}
this.$.model.applyFilter(and.length ? {where: {and}} : null);
}
removeFilter() {
this.$.filter = {};
this.applyFilter();
}
searchUser(search) {
if (/^[0-9]+$/.test(search)) {
return {id: search};
} else {
return {or: [
{name: search},
{nickname: {like: `%${search}%`}}
]}
}
}
} }
ngModule.vnComponent('vnLog', { ngModule.vnComponent('vnLog', {

View File

@ -0,0 +1,97 @@
import './index';
describe('Salix Component vnLog', () => {
let controller;
let $scope;
let $element;
beforeEach(ngModule('salix'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$element = angular.element('<vn-log></vn-log>');
controller = $componentController('vnLog', {$element, $scope});
}));
describe('relativeDate()', () => {
let date;
beforeEach(() => {
date = Date.vnNew();
});
it('should return empty string when date is null', () => {
const ret = controller.relativeDate(null);
expect(ret).toEqual('');
});
it('should return empty string when date is undefined', () => {
const ret = controller.relativeDate(undefined);
expect(ret).toEqual('');
});
it('should return today and time when date is today', () => {
const ret = controller.relativeDate(date);
expect(ret).toEqual('today 12:00');
});
it('should return yesterday and time when date is yesterday', () => {
date.setDate(date.getDate() - 1);
const ret = controller.relativeDate(date);
expect(ret).toEqual('yesterday 12:00');
});
it('should return abreviated weekday name and time when date is on past week', () => {
date.setDate(date.getDate() - 3);
const ret = controller.relativeDate(date);
expect(ret).toEqual('Fri 12:00');
});
it('should return abreviated month name, day number and time when date is on this year', () => {
date.setDate(date.getDate() + 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('21 Jan 12:00');
});
it('should return abreviated month name, day number, year and time when date is on different year', () => {
date.setDate(date.getDate() - 20);
const ret = controller.relativeDate(date);
expect(ret).toEqual('12/12/2000 12:00');
});
it('should convert to date and return string when date is not a Date class instance', () => {
const ret = controller.relativeDate(date.toJSON());
expect(ret).toEqual('today 12:00');
});
});
describe('castJsonValue()', () => {
it('should return date when string has valid JSON date format', () => {
const now = Date.vnNew();
const ret = controller.castJsonValue(now.toJSON());
expect(ret).toBeInstanceOf(Date);
});
it('should return same value when is string with invalid JSON date format', () => {
const ret = controller.castJsonValue('Foo');
expect(ret).toEqual('Foo');
});
it('should return same value when is not an string', () => {
const ret = controller.castJsonValue(1001);
expect(ret).toEqual(1001);
});
});
});

View File

@ -64,6 +64,9 @@ vn-log {
} }
} }
} }
.model-name {
text-transform: capitalize;
}
.model-value { .model-value {
font-style: italic; font-style: italic;
color: #c7bd2b; color: #c7bd2b;
@ -130,4 +133,5 @@ vn-log {
} }
} }
} }
.filter {}
} }

View File

@ -2,7 +2,7 @@
vn-fixed-price-search-panel vn-side-menu { vn-fixed-price-search-panel vn-side-menu {
.menu { .menu {
min-width: $right-menu-width; min-width: $menu-width;
} }
& > div { & > div {
.input { .input {

View File

@ -2,7 +2,7 @@
vn-travel-search-panel vn-side-menu { vn-travel-search-panel vn-side-menu {
.menu { .menu {
min-width: $right-menu-width; min-width: $menu-width;
} }
& > div { & > div {
.input { .input {