...
 
Commits (20)
......@@ -4,7 +4,7 @@
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json --host 0.0.0.0",
"start": "ng serve --base-href '/report_builder/' --proxy-config proxy.conf.json --host 0.0.0.0",
"build": "ng build",
"build-prod": "ng build --prod --output-hashing none",
"test": "ng test",
......
......@@ -27,14 +27,18 @@ export class NetworkErrorInterceptor implements HttpInterceptor {
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(request).catch((err: HttpErrorResponse) => {
// Respect base URL tag
const baseUrl = document.getElementsByTagName('base')[0].href;
const apiReq = request.clone({ url: `${baseUrl}${request.url}` });
return next.handle(apiReq).catch((err: HttpErrorResponse) => {
if (err.status === 0 || err.status === 500) {
// An error we can't help with happened, one of:
// 1. Network error
// 2. Client side JS error
// 3. Server side 500 error
this.store.dispatch(new CancelGeneratePreview());
this.snackBar.open('Sorry, something went wrong!');
this.snackBar.open('Sorry, something went wrong!', '', {duration: 5000});
return Observable.empty<HttpEvent<any>>();
}
return _throw(err.error);
......
......@@ -21,8 +21,7 @@ import {
@Injectable()
export class ApiService {
baseUrl = '/report_builder/';
apiUrl = this.baseUrl + 'api/';
apiUrl = 'api/';
constructor(private http: HttpClient) {}
......@@ -76,7 +75,7 @@ export class ApiService {
exportReport({ reportId, type }: { reportId: number; type: IExportType }) {
return this.http.get<IAsyncTaskId>(
this.baseUrl + `report/${reportId}/download_file/${type}/`
`report/${reportId}/download_file/${type}/`
);
}
......@@ -88,7 +87,7 @@ export class ApiService {
taskId: string;
}) {
return this.http.get<ITaskStatus>(
this.baseUrl + `report/${reportId}/check_status/${taskId}/`
`report/${reportId}/check_status/${taskId}/`
);
}
......
......@@ -227,7 +227,7 @@ export class ReportEffects {
@Effect({ dispatch: false })
cancelExportReport$ = this.actions$
.ofType(ReportActionTypes.CANCEL_EXPORT_REPORT)
.do(() => this.snackBar.open('Sorry, something went wrong!'));
.do(() => this.snackBar.open('Sorry, something went wrong!', '', {duration: 5000}));
@Effect()
createReport$ = this.actions$
......@@ -252,7 +252,7 @@ export class ReportEffects {
@Effect({ dispatch: false })
createReportError$ = this.actions$
.ofType(ReportActionTypes.CREATE_REPORT_ERROR)
.do(error => this.snackBar.open('Invalid report: please reload the page'));
.do(error => this.snackBar.open('Invalid report: please reload the page', '', {duration: 5000}));
@Effect()
copyReport$ = this.actions$
......
......@@ -18,7 +18,7 @@
<div class="example-header">
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Search Fields">
<input #searchFields matInput (keyup)="applyFilter($event.target.value)" placeholder="Search Fields">
</mat-form-field>
</div>
......
......@@ -4,6 +4,7 @@ import {
Output,
OnChanges,
EventEmitter,
ViewChild,
} from '@angular/core';
import { IField, IRelatedField } from '../../models/api';
import { MatTableDataSource } from '@angular/material';
......@@ -35,6 +36,8 @@ export class RightSidebarComponent implements OnChanges {
this.fieldDataSource = new MatTableDataSource(value);
}
@ViewChild('searchFields') searchInput
fieldDataSource: MatTableDataSource<IField>;
displayedColumnsField = ['name', 'button'];
nodes: TreeNode[];
......@@ -53,6 +56,7 @@ export class RightSidebarComponent implements OnChanges {
onActivate({ node }: { node: ITreeNode }) {
this.selectRelatedField.emit(node.data);
this.searchInput.nativeElement.value = ''
}
filterTree(text, tree) {
......
<div class="mat-row">
<div class="mat-cell">
<a mat-icon-button>
<mat-icon>reorder</mat-icon>
<a mat-icon-button disableRipple>
<mat-icon>drag_handle</mat-icon>
</a>
<button mat-icon-button (click)="deleteField.emit(field.position)">
<mat-icon>delete</mat-icon>
......
......@@ -50,9 +50,10 @@ export class DisplayTabComponent {
actionMapping: {
mouse: {
drop: (tree, node, event, { from: { data }, to: { index } }) => {
const newPos = data.position > index ? index : index - 1
this.updateField.emit({
id: data.position,
changes: { position: index - 1 },
changes: { position: newPos },
});
},
},
......
......@@ -49,7 +49,7 @@ export class FilterTabComponent {
drop: (tree, node, event, { from: { data }, to: { index } }) => {
this.updateFilter.emit({
id: data.position,
changes: { position: index - 1 },
changes: { position: index },
});
},
},
......
......@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<title>Django Report Builder</title>
<base href="/">
<base href="/report_builder/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
......
......@@ -354,12 +354,12 @@ amdefine@>=0.0.4:
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
angular-tree-component@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/angular-tree-component/-/angular-tree-component-7.0.1.tgz#fc8d0e72d8c34b87131a3ba2bd32ad20945689ac"
version "7.1.0"
resolved "https://registry.yarnpkg.com/angular-tree-component/-/angular-tree-component-7.1.0.tgz#53674ea944f7147647c7e48931f5fad66237a632"
dependencies:
lodash "4.17.4"
mobx ">=3"
mobx-angular ">=1"
lodash "^4.17.5"
mobx "^3.6.2"
mobx-angular "2.1.1"
ansi-html@0.0.7:
version "0.0.7"
......@@ -3778,14 +3778,14 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@4.17.4, lodash@^4.0.0, lodash@^4.5.0, lodash@~4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
lodash@^3.8.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.0, lodash@^4.5.0, lodash@~4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
......@@ -4057,13 +4057,13 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
dependencies:
minimist "0.0.8"
mobx-angular@>=1:
mobx-angular@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/mobx-angular/-/mobx-angular-2.1.1.tgz#d5e36539acb200186dd5a1170806b4776b9a8b88"
mobx@>=3:
version "3.4.1"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.4.1.tgz#37abe5ee882d401828d9f26c6c1a2f47614bbbef"
mobx@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.6.2.tgz#fb9f5ff5090539a1ad54e75dc4c098b602693320"
moment@^2.20.1:
version "2.20.1"
......
......@@ -22,6 +22,11 @@ class DisplayFieldSerializer(serializers.ModelSerializer):
'position', 'total', 'group', 'report', 'display_format',
'field_type')
read_only_fields = ('id',)
def to_internal_value(self, data):
if data.get('sort') is '':
data['sort'] = None
return super().to_internal_value(data)
class NonStrictCharField(serializers.CharField):
......
......@@ -20,7 +20,6 @@ from functools import reduce
import time
import datetime
import re
import freezegun
AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User')
......
......@@ -8,11 +8,13 @@ from ..models import (
get_limit_choices_to_callable)
from report_builder_demo.demo_models.models import (
Bar, Place, Restaurant, Waiter, Person, Child)
from report_builder.api.serializers import ReportNestedSerializer
from django.conf import settings
from rest_framework.test import APIClient
import time
import csv
import unittest
import json
from io import StringIO
from freezegun import freeze_time
from datetime import date, datetime, timedelta, time as dtime
......@@ -180,6 +182,29 @@ class ReportBuilderTests(TestCase):
for field in response.data:
self.assertEqual(field['can_filter'], True)
def test_report_builder_understands_empty_string(self):
ct = ContentType.objects.get_for_model(Report)
report = Report.objects.create(
name="foo report",
root_model=ct)
display_field = DisplayField.objects.create(
name='foo',
report=report,
field="X",
field_verbose="stuff",
sort=None,
position=1)
data = ReportNestedSerializer(report).data
data['displayfield_set'][0]['sort'] = ''
response = self.client.put(f'/report_builder/api/report/{report.id}/',
data=json.dumps(data),
content_type='application/json',
HTTP_X_REQUESTED_WWITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
self.assertIsNone(report.displayfield_set.all()[0].sort)
class ReportTests(TestCase):
def setUp(self):
......
......@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="django-report-builder",
version="5.0.0",
version="5.0.1",
author="David Burke",
author_email="david@burkesoftware.com",
description=("Query and Report builder for Django ORM"),
......