Browse Source

Performance (#501)

* Performance improvements.

* Date improvements.
pull/502/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
ac6a49d9b1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  2. 19
      backend/src/Squidex.Infrastructure/Json/Newtonsoft/DateConverter.cs
  3. 5
      backend/src/Squidex/Areas/Api/Controllers/Statistics/Models/CallsUsagePerDateDto.cs
  4. 3
      backend/src/Squidex/Areas/Api/Controllers/Statistics/Models/StorageUsagePerDateDto.cs
  5. 1
      backend/src/Squidex/Squidex.csproj
  6. 5
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs
  7. 1
      frontend/app-config/webpack.config.js
  8. 9
      frontend/app/features/dashboard/pages/dashboard-page.component.ts
  9. 2
      frontend/app/framework/angular/forms/editors/date-time-editor.component.scss
  10. 90
      frontend/app/framework/angular/forms/editors/date-time-editor.component.ts
  11. 2
      frontend/app/framework/angular/forms/validators.ts
  12. 7
      frontend/app/framework/angular/image-source.directive.ts
  13. 6
      frontend/app/framework/angular/pipes/date-time.pipes.spec.ts
  14. 12
      frontend/app/framework/angular/pipes/date-time.pipes.ts
  15. 23
      frontend/app/framework/utils/date-helper.spec.ts
  16. 20
      frontend/app/framework/utils/date-helper.ts
  17. 151
      frontend/app/framework/utils/date-time.spec.ts
  18. 152
      frontend/app/framework/utils/date-time.ts
  19. 18
      frontend/app/framework/utils/duration.ts
  20. 8
      frontend/app/framework/utils/string-helper.ts
  21. 6
      frontend/app/shared/components/assets/asset.component.html
  22. 19
      frontend/app/shared/components/assets/asset.component.scss
  23. 8
      frontend/app/shared/services/apps.service.spec.ts
  24. 4
      frontend/app/shared/services/apps.service.ts
  25. 8
      frontend/app/shared/services/assets.service.spec.ts
  26. 4
      frontend/app/shared/services/assets.service.ts
  27. 12
      frontend/app/shared/services/backups.service.spec.ts
  28. 8
      frontend/app/shared/services/backups.service.ts
  29. 6
      frontend/app/shared/services/comments.service.spec.ts
  30. 6
      frontend/app/shared/services/comments.service.ts
  31. 12
      frontend/app/shared/services/contents.service.spec.ts
  32. 6
      frontend/app/shared/services/contents.service.ts
  33. 4
      frontend/app/shared/services/history.service.spec.ts
  34. 2
      frontend/app/shared/services/history.service.ts
  35. 14
      frontend/app/shared/services/rules.service.spec.ts
  36. 10
      frontend/app/shared/services/rules.service.ts
  37. 16
      frontend/app/shared/services/schemas.service.spec.ts
  38. 8
      frontend/app/shared/services/schemas.service.ts
  39. 12
      frontend/app/shared/services/usages.service.spec.ts
  40. 12
      frontend/app/shared/services/usages.service.ts
  41. 8
      frontend/app/shared/state/contents.forms.spec.ts
  42. 10
      frontend/app/shared/state/contents.forms.ts
  43. 11
      frontend/package-lock.json
  44. 2
      frontend/package.json

10
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -209,16 +209,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
return null;
}
private static string? ContentStatus(EnrichedEvent @event)
{
if (@event is EnrichedContentEvent contentEvent)
{
return contentEvent.Status.ToString();
}
return null;
}
private string? AssetContentUrl(EnrichedEvent @event)
{
if (@event is EnrichedAssetEvent assetEvent)

19
backend/src/Squidex.Infrastructure/Json/Newtonsoft/DateConverter.cs

@ -0,0 +1,19 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json.Converters;
namespace Squidex.Infrastructure.Json.Newtonsoft
{
public sealed class DateConverter : IsoDateTimeConverter
{
public DateConverter()
{
DateTimeFormat = "yyyy-MM-dd";
}
}
}

5
backend/src/Squidex/Areas/Api/Controllers/Statistics/Models/CallsUsagePerDateDto.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.UsageTracking;
namespace Squidex.Areas.Api.Controllers.Statistics.Models
@ -15,6 +17,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics.Models
/// <summary>
/// The date when the usage was tracked.
/// </summary>
[JsonConverter(typeof(DateConverter))]
public DateTime Date { get; set; }
/// <summary>
@ -36,7 +39,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics.Models
{
var result = new CallsUsagePerDateDto
{
Date = stats.Date,
Date = DateTime.SpecifyKind(stats.Date, DateTimeKind.Utc),
TotalBytes = stats.TotalBytes,
TotalCalls = stats.TotalCalls,
AverageElapsedMs = stats.AverageElapsedMs,

3
backend/src/Squidex/Areas/Api/Controllers/Statistics/Models/StorageUsagePerDateDto.cs

@ -6,7 +6,9 @@
// ==========================================================================
using System;
using System.Text.Json.Serialization;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Areas.Api.Controllers.Statistics.Models
{
@ -15,6 +17,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics.Models
/// <summary>
/// The date when the usage was tracked.
/// </summary>
[JsonConverter(typeof(DateConverter))]
public DateTime Date { get; set; }
/// <summary>

1
backend/src/Squidex/Squidex.csproj

@ -50,6 +50,7 @@
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="3.1.2" />
<PackageReference Include="MongoDB.Driver" Version="2.10.2" />
<PackageReference Include="Namotion.Reflection" Version="1.0.10" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NJsonSchema" Version="10.1.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.2.3" />
<PackageReference Include="OpenCover" Version="4.7.922" PrivateAssets="all" />

5
backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/TestData/FakeUrlGenerator.cs

@ -51,11 +51,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.TestData
throw new NotSupportedException();
}
public string AssetSource(Guid assetId)
{
throw new NotSupportedException();
}
public string BackupsUI(NamedId<Guid> appId)
{
throw new NotSupportedException();

1
frontend/app-config/webpack.config.js

@ -169,7 +169,6 @@ module.exports = function (env) {
plugins: [
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)fesm5/, root('./app'), {}),
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
/**
* Puts each bundle into a file and appends the hash of the file to the path.

9
frontend/app/features/dashboard/pages/dashboard-page.component.ts

@ -135,9 +135,12 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
this.history = dto;
}));
const dateTo = DateTime.today().toStringFormat('yyyy-MM-dd');
const dateFrom = DateTime.today().addDays(-20).toStringFormat('yyyy-MM-dd');
this.own(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getStorageUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
switchMap(app => this.usagesService.getStorageUsages(app.name, dateFrom, dateTo)))
.subscribe(dtos => {
const labels = createLabels(dtos);
@ -174,7 +177,7 @@ export class DashboardPageComponent extends ResourceOwner implements OnInit {
this.own(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getCallsUsages(app.name, DateTime.today().addDays(-20), DateTime.today())))
switchMap(app => this.usagesService.getCallsUsages(app.name, dateFrom, dateTo)))
.subscribe(({ details, totalBytes, totalCalls, allowedCalls, averageElapsedMs }) => {
const labels = createLabelsFromSet(details);
@ -234,7 +237,7 @@ function label(category: string) {
}
function createLabels(dtos: ReadonlyArray<{ date: DateTime }>): ReadonlyArray<string> {
return dtos.map(d => d.date.toStringFormat('M-DD'));
return dtos.map(d => d.date.toStringFormat('M-dd'));
}
function createLabelsFromSet(dtos: { [category: string]: ReadonlyArray<{ date: DateTime }> }): ReadonlyArray<string> {

2
frontend/app/framework/angular/forms/editors/date-time-editor.component.scss

@ -26,7 +26,7 @@
}
.form-date {
width: 7rem;
width: 8.5rem;
}
.form-date-only {

90
frontend/app/framework/angular/forms/editors/date-time-editor.component.ts

@ -7,11 +7,11 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import moment from 'moment';
import {
DateHelper,
DateTime,
StatefulControlComponent,
Types,
UIOptions
} from '@app/framework/internal';
@ -38,8 +38,7 @@ const NO_EMIT = { emitEvent: false };
})
export class DateTimeEditorComponent extends StatefulControlComponent<{}, string | null> implements OnInit, AfterViewInit, FocusComponent {
private picker: any;
private timeValue: moment.Moment | null = null;
private dateValue: moment.Moment | null = null;
private dateTime: DateTime | null;
private hideDateButtonsSettings: boolean;
private suppressEvents = false;
@ -70,7 +69,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
public get hasValue() {
return !!this.dateValue;
return !!this.dateTime;
}
constructor(changeDetector: ChangeDetectorRef, uiOptions: UIOptions) {
@ -81,40 +80,21 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
public ngOnInit() {
this.own(
this.timeControl.valueChanges.subscribe(value => {
if (!value || value.length === 0) {
this.timeValue = null;
} else {
this.timeValue = moment.utc(value, 'HH:mm:ss');
}
this.timeControl.valueChanges.subscribe(() => {
this.callChangeFormatted();
}));
this.own(
this.dateControl.valueChanges.subscribe(value => {
if (!value || value.length === 0) {
this.dateValue = null;
} else {
this.dateValue = moment.utc(value, 'YYYY-MM-DD');
}
this.dateControl.valueChanges.subscribe(() => {
this.callChangeFormatted();
}));
}
public writeValue(obj: any) {
if (Types.isString(obj) && obj.length > 0) {
const parsed = moment.parseZone(obj);
this.dateValue = parsed;
if (this.showTime) {
this.timeValue = parsed;
}
} else {
this.timeValue = null;
this.dateValue = null;
try {
this.dateTime = DateTime.parseISO(obj);
} catch (ex) {
this.dateTime = null;
}
this.updateControls();
@ -142,9 +122,9 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
if (this.suppressEvents) {
return;
}
this.dateValue = this.picker.getMoment();
this.callChangeFormatted();
this.dateControl.setValue(this.picker.toString('YYYY-MM-DD'));
this.callTouched();
}
});
@ -153,7 +133,7 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
public writeNow() {
this.writeValue(new Date().toUTCString());
this.writeValue(DateTime.now().toISOString());
this.updateControls();
this.callChangeFormatted();
@ -163,10 +143,9 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
public reset() {
this.timeControl.setValue(null, NO_EMIT);
this.dateControl.setValue(null, NO_EMIT);
this.dateTime = null;
this.dateValue = null;
this.updateControls();
this.callChange(null);
this.callTouched();
@ -179,22 +158,28 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
}
private getValue(): string | null {
if (!this.dateValue || !this.dateValue.isValid()) {
if (!this.dateControl.value) {
return null;
}
if (this.timeValue && !this.timeValue.isValid()) {
return null;
let result: string | null = null;
if (this.showTime && this.timeControl.value) {
const combined = `${this.dateControl.value}T${this.timeControl.value}`;
const parsed = DateTime.tryParseISO(combined, true);
if (parsed) {
result = parsed.toISOString();
}
}
let result = this.dateValue.format('YYYY-MM-DD');
if (!result) {
const parsed = DateTime.tryParseISO(this.dateControl.value, true);
if (this.showTime && this.timeValue) {
result += 'T';
result += this.timeValue.format('HH:mm:ss');
result += 'Z';
} else if (this.enforceTime) {
result += 'T00:00:00Z';
if (parsed) {
result = parsed.toISOString();
}
}
return result;
@ -203,19 +188,18 @@ export class DateTimeEditorComponent extends StatefulControlComponent<{}, string
private updateControls() {
this.suppressEvents = true;
if (this.timeValue && this.timeValue.isValid()) {
this.timeControl.setValue(this.timeValue.format('HH:mm:ss'), NO_EMIT);
if (this.dateTime && this.mode === 'DateTime') {
this.timeControl.setValue(this.dateTime.toStringFormatUTC('HH:mm:ss'), NO_EMIT);
} else {
this.timeControl.setValue(null, NO_EMIT);
}
if (this.dateValue && this.dateValue.isValid() && this.picker) {
const dateString = this.dateValue.format('YYYY-MM-DD');
const dateLocal = moment(dateString);
if (this.dateTime && this.picker) {
const dateString = this.dateTime.toStringFormatUTC('yyyy-MM-dd');
this.dateControl.setValue(dateString, NO_EMIT);
this.picker.setDate(DateHelper.getUTCDate(this.dateTime.raw), true);
this.picker.setMoment(dateLocal);
this.dateControl.setValue(dateString, NO_EMIT);
} else {
this.dateControl.setValue(null, NO_EMIT);
}

2
frontend/app/framework/angular/forms/validators.ts

@ -66,7 +66,7 @@ export module ValidatorsEx {
if (v) {
try {
DateTime.parseISO_UTC(v);
DateTime.parseISO(v);
} catch (e) {
return { validdatetime: false };
}

7
frontend/app/framework/angular/image-source.directive.ts

@ -8,6 +8,7 @@
import { AfterViewInit, Directive, ElementRef, Input, NgZone, OnChanges, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { MathHelper, ResourceOwner } from '@app/framework/internal';
import { StringHelper } from '../utils/string-helper';
const LAYOUT_CACHE: { [key: string]: { width: number, height: number } } = {};
@ -122,10 +123,12 @@ export class ImageSourceDirective extends ResourceOwner implements OnChanges, On
const h = Math.round(this.size.height);
if (w > 0 && h > 0) {
let source = `${this.imageSource}&width=${w}&height=${h}&mode=Pad&nofocus`;
let source = this.imageSource;
source = StringHelper.appendToUrl(source, `${this.imageSource}&width=${w}&height=${h}&mode=Pad&nofocus`);
if (this.loadQuery) {
source += `&q=${this.loadQuery}`;
source = StringHelper.appendToUrl(source, 'q', this.loadQuery);
}
this.renderer.setProperty(this.element.nativeElement, 'src', source);

6
frontend/app/framework/angular/pipes/date-time.pipes.spec.ts

@ -20,7 +20,7 @@ import {
ShortTimePipe
} from './date-time.pipes';
const dateTime = DateTime.parse('2013-10-03T12:13:14.125', DateTime.iso8601());
const dateTime = DateTime.parseISO('2013-10-03T12:13:14.125', false);
describe('DurationPipe', () => {
it('should format to standard duration string', () => {
@ -86,7 +86,7 @@ describe('DayOfWeekPipe', () => {
const pipe = new DayOfWeekPipe();
const actual = pipe.transform(dateTime);
const expected = 'Th';
const expected = 'Thu';
expect(actual).toBe(expected);
});
@ -124,7 +124,7 @@ describe('FullDateTimePipe', () => {
const pipe = new FullDateTimePipe();
const actual = pipe.transform(dateTime);
const expected = 'October 3, 2013 12:13 PM';
const expected = 'Oct 3, 2013, 12:13:14 PM';
expect(actual).toBe(expected);
});

12
frontend/app/framework/angular/pipes/date-time.pipes.ts

@ -19,7 +19,7 @@ export class ShortDatePipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('DD. MMM');
return value.toStringFormat('dd. MMM');
}
}
@ -47,7 +47,7 @@ export class DatePipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('DD. MMM YYYY');
return value.toStringFormat('dd. LLL yyyy');
}
}
@ -61,7 +61,7 @@ export class MonthPipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('MMMM');
return value.toStringFormat('LLLL');
}
}
@ -89,7 +89,7 @@ export class DayOfWeekPipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('dd');
return value.toStringFormat('E');
}
}
@ -103,7 +103,7 @@ export class DayPipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('DD');
return value.toStringFormat('dd');
}
}
@ -131,7 +131,7 @@ export class FullDateTimePipe implements PipeTransform {
return fallback;
}
return value.toStringFormat('LLL');
return value.toStringFormat('PPpp');
}
}

23
frontend/app/framework/utils/date-helper.spec.ts

@ -5,27 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import moment from 'moment';
import { DateHelper } from './date-helper';
describe('DateHelper', () => {
it('should call config method of moment object', () => {
let called: string;
DateHelper.setMoment({
locale: (l: string) => { called = l; }
});
it('should use default locale if not configured', () => {
DateHelper.setlocale(null);
DateHelper.locale('en');
const locale = DateHelper.getLocale();
expect(called!).toBe('en');
expect(locale).toBe('en');
});
it('should use global moment if not configured', () => {
DateHelper.setMoment(null);
DateHelper.locale('en');
it('should use configured locale', () => {
DateHelper.setlocale('it');
const locale = DateHelper.getLocale();
expect(moment.locale()).toBe('en');
expect(locale).toBe('it');
});
});

20
frontend/app/framework/utils/date-helper.ts

@ -5,16 +5,22 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import moment from 'moment';
export module DateHelper {
let momentInstance: any;
let locale: string | null;
export function setlocale(code: string | null) {
locale = code;
}
export function getLocale() {
return locale || 'en';
}
export function setMoment(value: any) {
momentInstance = value;
export function getUTCDate(date: Date) {
return new Date(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
}
export function locale(code: string) {
(momentInstance || moment).locale(code);
export function getLocalDate (date: Date) {
return new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000);
}
}

151
frontend/app/framework/utils/date-time.spec.ts

@ -13,49 +13,39 @@ describe('DateTime', () => {
const now = DateTime.now();
it('should parse from iso string', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14.125');
const actual = DateTime.parseISO('2013-10-16T12:13:14.125', false);
expect(value.year).toBe(2013);
expect(value.month).toBe(10);
expect(value.day).toBe(16);
expect(value.hours).toBe(12);
expect(value.minutes).toBe(13);
expect(value.seconds).toBe(14);
expect(value.milliseconds).toBe(125);
expect(value.weekDay).toBe(3);
expect(actual.year).toBe(2013);
expect(actual.month).toBe(10);
expect(actual.day).toBe(16);
expect(actual.hours).toBe(12);
expect(actual.minutes).toBe(13);
expect(actual.seconds).toBe(14);
expect(actual.milliseconds).toBe(125);
expect(actual.weekDay).toBe(3);
expect(value.raw).not.toBeNull();
expect(actual.raw).not.toBeNull();
});
it('should throw when date string to parse is null', () => {
expect(() => DateTime.parseISO('#')).toThrow();
expect(() => DateTime.parseISO(null!)).toThrow();
});
it('should throw when date string to parse is invalid', () => {
expect(() => DateTime.parse('#', 'yyyy-MM-dd')).toThrow();
});
it('should throw when utc date string to parse is invalid', () => {
expect(() => DateTime.parseUTC('#', 'yyyy-MM-dd')).toThrow();
expect(() => DateTime.parseISO('#')).toThrow();
});
it('should parse Microsoft date format', () => {
const actual = DateTime.parseMSDate('/Date(1224043200000)/');
const expected = DateTime.parseISO('2008-10-15T04:00:00');
expect(actual).toEqual(expected);
it('should return null when date string to try parse is null', () => {
expect(DateTime.tryParseISO(null!)).toBeNull();
});
it('should parse Microsoft date format with positive offset', () => {
const actual = DateTime.parseMSDate('/Date(1224043200000+2)/');
const expected = DateTime.parseISO('2008-10-15T06:00:00');
expect(actual).toEqual(expected);
it('should return null when date string to try parse is invalid', () => {
expect(DateTime.tryParseISO(null!)).toBeNull();
});
it('should parse Microsoft date format with negative offset', () => {
const actual = DateTime.parseMSDate('/Date(1224043200000-2)/');
const expected = DateTime.parseISO('2008-10-15T02:00:00');
it('should parse date from utc date', () => {
const actual = DateTime.parseISO('2013-10-16');
const expected = DateTime.parseISO('2013-10-16T00:00:00Z');
expect(actual).toEqual(expected);
});
@ -82,19 +72,47 @@ describe('DateTime', () => {
});
it('should print to formatted string', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14');
const value = DateTime.parseISO('2013-10-16T12:13:14', false);
const expected = '12:13';
expect(value.toStringFormat('HH:mm')).toEqual(expected);
});
it('should print to formatted ISO string', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14.123Z');
const expected = '12:13';
expect(value.toStringFormatUTC('HH:mm')).toEqual(expected);
});
it('should print to iso string', () => {
const value = DateTime.parseISO_UTC('2013-10-16T12:13:14');
const expected = '2013-10-16T12:13:14.000Z';
const value = DateTime.parseISO('2013-10-16T12:13:14.123Z');
const expected = '2013-10-16T12:13:14Z';
expect(value.toISOString()).toEqual(expected);
});
it('should print to iso string with milliseconds', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14.123Z');
const expected = '2013-10-16T12:13:14.123Z';
expect(value.toISOString(false)).toEqual(expected);
});
it('should print to iso date', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14Z');
const expected = '2013-10-16';
expect(value.toISODate()).toEqual(expected);
});
it('should print to iso utc date', () => {
const value = DateTime.parseISO('2013-10-16T12:13:14Z');
const expected = '2013-10-16';
expect(value.toISODate()).toEqual(expected);
});
it('should print to from now string', () => {
const value = DateTime.now().addMinutes(-4);
const expected = '4 minutes ago';
@ -102,38 +120,65 @@ describe('DateTime', () => {
expect(value.toFromNow()).toBe(expected);
});
it('should print from format with underscore', () => {
const actual = DateTime.parseISO('2013-10-16T00:00:00');
const expected = DateTime.parse('10_2013_16', 'MM_YYYY_DD');
it('should calculate valid first of week', () => {
const actual = DateTime.parseISO('2013-10-16T12:13:14.125', false).firstOfWeek();
const expected = DateTime.parseISO('2013-10-14', false);
expect(actual).toEqual(expected);
});
it('should calculate valid first of week', () => {
const actual = DateTime.parseISO_UTC('2013-10-16T12:13:14.125').firstOfWeek();
const expected = DateTime.parseISO_UTC('2013-10-14T00:00:00');
it('should calculate valid first of month', () => {
const actual = DateTime.parseISO('2013-10-16T12:13:14.125', false).firstOfMonth();
const expected = DateTime.parseISO('2013-10-01', false);
expect(actual.toISOString()).toEqual(expected.toISOString());
});
it('should add years to date time', () => {
const actual = DateTime.parseISO('2013-01-01T12:12:12.100Z').addYears(2);
const expected = DateTime.parseISO('2015-01-01T12:12:12.100Z');
expect(actual).toEqual(expected);
});
it('should calculate valid first of month', () => {
const actual = DateTime.parseISO_UTC('2013-10-16T12:13:14.125').firstOfMonth();
const expected = DateTime.parseISO_UTC('2013-10-01');
it('should add months to date time', () => {
const actual = DateTime.parseISO('2015-01-01T12:12:12.100Z').addMonths(1);
const expected = DateTime.parseISO('2015-02-01T12:12:12.100Z');
expect(actual.toISOString()).toEqual(expected.toISOString());
expect(actual).toEqual(expected);
});
it('should add days to date time', () => {
const actual = DateTime.parseISO('2015-02-01T12:12:12.100Z').addDays(9);
const expected = DateTime.parseISO('2015-02-10T12:12:12.100Z');
expect(actual).toEqual(expected);
});
it('should add hours to date time', () => {
const actual = DateTime.parseISO('2015-02-10T12:12:12.100Z').addHours(11);
const expected = DateTime.parseISO('2015-02-10T23:12:12.100Z');
expect(actual).toEqual(expected);
});
it('should add minutes to date time', () => {
const actual = DateTime.parseISO('2015-02-10T23:12:12.100Z').addMinutes(7);
const expected = DateTime.parseISO('2015-02-10T23:19:12.100Z');
expect(actual).toEqual(expected);
});
it('should add seconds to date time', () => {
const actual = DateTime.parseISO('2015-02-10T23:19:12.100Z').addSeconds(5);
const expected = DateTime.parseISO('2015-02-10T23:19:17.100Z');
expect(actual).toEqual(expected);
});
it('should add various offsets to date time', () => {
const actual =
DateTime.parseISO_UTC('2013-05-01T12:12:12.100')
.addYears(1)
.addMonths(2)
.addDays(13)
.addHours(3)
.addMinutes(10)
.addSeconds(15)
.addMilliseconds(125);
const expected = DateTime.parseISO_UTC('2014-07-16T15:22:27.225');
it('should add milliseconds to date time', () => {
const actual = DateTime.parseISO('2015-02-10T23:19:17.100Z').addMilliseconds(125);
const expected = DateTime.parseISO('2015-02-10T23:19:17.225Z');
expect(actual).toEqual(expected);
});

152
frontend/app/framework/utils/date-time.ts

@ -5,7 +5,11 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import moment from 'moment';
import { addDays, addHours, addMilliseconds, addMinutes, addMonths, addSeconds, addYears, format, formatDistanceToNow, parse, parseISO, startOfDay, startOfMonth, startOfTomorrow, startOfWeek, startOfYesterday } from 'date-fns';
import { DateHelper } from './date-helper';
const DATE_FORMAT = 'yyyy-MM-dd';
export class DateTime {
public get raw(): Date {
@ -49,83 +53,61 @@ export class DateTime {
}
public get date(): DateTime {
const clone = this.cloneDate();
clone.setUTCHours(0, 0, 0, 0);
return new DateTime(clone);
return new DateTime(startOfDay(this.value));
}
constructor(private readonly value: Date) {
Object.freeze(this);
}
public static iso8601(): any {
return moment.ISO_8601;
}
public static now(): DateTime {
return new DateTime(new Date());
}
public static today(): DateTime {
return DateTime.now().date;
return new DateTime(startOfDay(new Date()));
}
public static tomorrow(): DateTime {
return DateTime.today().addDays(1);
return new DateTime(startOfTomorrow());
}
public static yesterday(): DateTime {
return DateTime.today().addDays(-1);
return new DateTime(startOfYesterday());
}
public static parseMSDate(value: string): DateTime {
let off = parseInt(value.substr(19, 3), 10);
public static parseISO(value: string, assumeUtc = true): DateTime {
const result = DateTime.tryParseISO(value, assumeUtc);
if (isNaN(off)) {
off = 0;
if (!result) {
throw new Error(`${value} is not a valid datetime.`);
}
const date = new Date(parseInt(value.substr(6), 10));
const time = (date.getTime());
const offs = (date.getTimezoneOffset() + off * 60) * 60000;
date.setTime(time + offs);
return new DateTime(date);
}
public static parseISO(value: string): DateTime {
return DateTime.parse(value, DateTime.iso8601());
return result;
}
public static parseISO_UTC(value: string): DateTime {
return DateTime.parseUTC(value, DateTime.iso8601());
}
public static tryParseISO(value: string, assumeUtc = true): DateTime | null {
if (!value) {
return null;
}
public static parse(value: string, format: string): DateTime {
const parsedMoment = moment(value, format);
let date: Date;
if (parsedMoment.isValid()) {
return new DateTime(parsedMoment.toDate());
if (value.length === DATE_FORMAT.length) {
date = parse(value, DATE_FORMAT, new Date());
} else {
throw Error(`DateTime: ${value} is not a valid date time string`);
date = date = parseISO(value);
}
}
public static parseUTC(value: string, format: string): DateTime {
const parsedMoment = moment.utc(value, format);
if (isNaN(date.getTime())) {
return null;
}
if (parsedMoment.isValid()) {
return new DateTime(parsedMoment.toDate());
} else {
throw Error(`DateTime: ${value} is not a valid date time string`);
if (assumeUtc && (value.length === DATE_FORMAT.length || !value.endsWith('Z'))) {
date = DateHelper.getLocalDate(date);
}
}
private cloneDate(): Date {
return new Date(this.value.getTime());
return new DateTime(date);
}
public eq(v: DateTime): boolean {
@ -153,86 +135,68 @@ export class DateTime {
}
public firstOfWeek(): DateTime {
const date = this.date;
return date.addDays(-date.value.getUTCDay() + 1);
return new DateTime(startOfWeek(this.value, { weekStartsOn: 1 }));
}
public firstOfMonth(): DateTime {
const monthStart = new Date(Date.UTC(this.year, this.month - 1, 1));
return new DateTime(monthStart);
return new DateTime(startOfMonth(this.value));
}
public addYears(value: number): DateTime {
const clone = this.cloneDate();
clone.setUTCFullYear(clone.getUTCFullYear() + value, clone.getUTCMonth(), clone.getUTCDay());
return new DateTime(clone);
return new DateTime(addYears(this.value, value));
}
public addMonths(value: number): DateTime {
const clone = this.cloneDate();
clone.setUTCMonth(clone.getUTCMonth() + value, clone.getUTCDate());
return new DateTime(clone);
return new DateTime(addMonths(this.value, value));
}
public addDays(value: number): DateTime {
const clone = this.cloneDate();
clone.setUTCDate(clone.getUTCDate() + value);
return new DateTime(clone);
return new DateTime(addDays(this.value, value));
}
public addHours(value: number): DateTime {
const clone = this.cloneDate();
clone.setTime(clone.getTime() + (value * 60 * 60 * 1000));
return new DateTime(clone);
return new DateTime(addHours(this.value, value));
}
public addMinutes(value: number): DateTime {
const clone = this.cloneDate();
clone.setTime(clone.getTime() + (value * 60 * 1000));
return new DateTime(clone);
return new DateTime(addMinutes(this.value, value));
}
public addSeconds(value: number): DateTime {
const clone = this.cloneDate();
clone.setTime(clone.getTime() + (value * 1000));
return new DateTime(clone);
return new DateTime(addSeconds(this.value, value));
}
public addMilliseconds(value: number): DateTime {
const clone = this.cloneDate();
clone.setTime(clone.getTime() + value);
return new DateTime(clone);
return new DateTime(addMilliseconds(this.value, value));
}
public toISOString(): string {
return moment(this.value).toISOString();
public toISODateUTC(): string {
return format(DateHelper.getUTCDate(this.value), DATE_FORMAT);
}
public toStringFormat(format: string): string {
return moment(this.value).format(format);
public toISODate(): string {
return format(this.value, DATE_FORMAT);
}
public toStringFormat(pattern: string): string {
return format(this.value, pattern);
}
public toUTCStringFormat(format: string): string {
return moment.utc(this.value).format(format);
public toStringFormatUTC(pattern: string): string {
return format(DateHelper.getUTCDate(this.value), pattern);
}
public toFromNow(): string {
return moment.utc(this.value).fromNow();
return formatDistanceToNow(this.value, { includeSeconds: true, addSuffix: true });
}
public toISOString(withoutMilliseconds = true): string {
let result = this.value.toISOString();
if (withoutMilliseconds) {
result = result.slice(0, 19) + 'Z';
}
return result;
}
}

18
frontend/app/framework/utils/duration.ts

@ -5,8 +5,6 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import moment from 'moment';
import { DateTime } from './date-time';
export class Duration {
@ -23,21 +21,29 @@ export class Duration {
}
public toString(): string {
const duration = moment.duration(this.value);
let seconds = this.value / 1000;
const hours = Math.floor(seconds / 3600);
let hoursString = Math.floor(duration.asHours()).toString();
let hoursString = hours.toString();
if (hoursString.length === 1) {
hoursString = `0${hoursString}`;
}
let minutesString = duration.minutes().toString();
seconds = seconds % 3600;
const minutes = Math.floor(seconds / 60);
let minutesString = minutes.toString();
if (minutesString.length === 1) {
minutesString = `0${minutesString}`;
}
let secondsString = duration.seconds().toString();
seconds = seconds % 60;
let secondsString = seconds.toString();
if (secondsString.length === 1) {
secondsString = `0${secondsString}`;

8
frontend/app/framework/utils/string-helper.ts

@ -20,14 +20,18 @@ export module StringHelper {
return '';
}
export function appendToUrl(url: string, key: string, value: any) {
export function appendToUrl(url: string, key: string, value?: any) {
if (url.indexOf('?') > 0) {
url += '&';
} else {
url += '?';
}
url += `${key}=${value}`;
if (value !== undefined) {
url += `${key}=${value}`;
} else {
url += key;
}
return url;
}

6
frontend/app/shared/components/assets/asset.component.html

@ -81,11 +81,7 @@
</div>
</div>
<div class="file-tags tags">
<sqx-tag-editor [ngModel]="asset.tags"
styleBlank="true"
styleGray="true"
readonly="true">
</sqx-tag-editor>
<div class="tag" *ngFor="let tag of asset.tags">{{tag}}</div>
</div>
<div class="file-info">
{{asset.metadataText}}

19
frontend/app/shared/components/assets/asset.component.scss

@ -288,6 +288,19 @@ img {
cursor: pointer;
}
.tags {
min-height: 26px;
}
.icon-close {
font-size: .6rem;
}
.tag {
background: $color-border;
border: 0;
border-radius: 2px;
color: $color-text;
display: inline-block;
margin-bottom: .25rem;
margin-right: 2px;
padding: 1px .5rem;
vertical-align: top;
white-space: nowrap;
}

8
frontend/app/shared/services/apps.service.spec.ts

@ -232,8 +232,8 @@ describe('AppsService', () => {
label: `my-label${id}${suffix}`,
description: `my-description${id}${suffix}`,
permissions: ['Owner'],
created: `${id % 1000 + 2000}-12-12T10:10:00`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`,
canAccessApi: id % 2 === 0,
canAccessContent: id % 2 === 0,
planName: 'Free',
@ -257,8 +257,8 @@ export function createApp(id: number, suffix = '') {
`my-label${id}${suffix}`,
`my-description${id}${suffix}`,
['Owner'],
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`),
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`),
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`),
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`),
id % 2 === 0,
id % 2 === 0,
'Free', 'Basic',

4
frontend/app/shared/services/apps.service.ts

@ -229,8 +229,8 @@ function parseApp(response: any) {
response.label,
response.description,
response.permissions,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
DateTime.parseISO(response.created),
DateTime.parseISO(response.lastModified),
response.canAccessApi,
response.canAccessContent,
response.planName,

8
frontend/app/shared/services/assets.service.spec.ts

@ -451,9 +451,9 @@ describe('AssetsService', () => {
return {
id: `id${id}`,
created: `${id % 1000 + 2000}-12-12T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
createdBy: `creator${id}`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`,
lastModifiedBy: `modifier${id}`,
fileName: `My Name${id}${suffix}.png`,
fileHash: `My Hash${id}${suffix}`,
@ -510,8 +510,8 @@ export function createAsset(id: number, tags?: ReadonlyArray<string>, suffix = '
return new AssetDto(links, meta,
`id${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), `creator${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), `modifier${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`), `creator${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
`My Name${id}${suffix}.png`,
`My Hash${id}${suffix}`,
'png',

4
frontend/app/shared/services/assets.service.ts

@ -418,8 +418,8 @@ export class AssetsService {
function parseAsset(response: any) {
return new AssetDto(response._links, response._meta,
response.id,
DateTime.parseISO_UTC(response.created), response.createdBy,
DateTime.parseISO_UTC(response.lastModified), response.lastModifiedBy,
DateTime.parseISO(response.created), response.createdBy,
DateTime.parseISO(response.lastModified), response.lastModifiedBy,
response.fileName,
response.fileHash,
response.fileType,

12
frontend/app/shared/services/backups.service.spec.ts

@ -93,8 +93,8 @@ describe('BackupsService', () => {
expect(restore!).toEqual(
new RestoreDto('http://url',
DateTime.parseISO_UTC('2017-02-03'),
DateTime.parseISO_UTC('2017-02-04'),
DateTime.parseISO('2017-02-03'),
DateTime.parseISO('2017-02-04'),
'Failed',
[
'log1',
@ -194,8 +194,8 @@ describe('BackupsService', () => {
function backupResponse(id: number) {
return {
id: `id${id}`,
started: `${id % 1000 + 2000}-12-12T10:10:00`,
stopped: id % 2 === 0 ? `${id % 1000 + 2000}-11-11T10:10:00` : null,
started: `${id % 1000 + 2000}-12-12T10:10:00Z`,
stopped: id % 2 === 0 ? `${id % 1000 + 2000}-11-11T10:10:00Z` : null,
handledEvents: id * 17,
handledAssets: id * 23,
status: id % 2 === 0 ? 'Success' : 'Failed',
@ -213,8 +213,8 @@ export function createBackup(id: number) {
return new BackupDto(links,
`id${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`),
id % 2 === 0 ? DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`) : null,
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`),
id % 2 === 0 ? DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`) : null,
id * 17,
id * 23,
id % 2 === 0 ? 'Success' : 'Failed');

8
frontend/app/shared/services/backups.service.ts

@ -148,8 +148,8 @@ export class BackupsService {
function parseRestore(response: any) {
return new RestoreDto(
response.url,
DateTime.parseISO_UTC(response.started),
response.stopped ? DateTime.parseISO_UTC(response.stopped) : null,
DateTime.parseISO(response.started),
response.stopped ? DateTime.parseISO(response.stopped) : null,
response.status,
response.log);
}
@ -157,8 +157,8 @@ function parseRestore(response: any) {
function parseBackup(response: any) {
return new BackupDto(response._links,
response.id,
DateTime.parseISO_UTC(response.started),
response.stopped ? DateTime.parseISO_UTC(response.stopped) : null,
DateTime.parseISO(response.started),
response.stopped ? DateTime.parseISO(response.stopped) : null,
response.handledEvents,
response.handledAssets,
response.status);

6
frontend/app/shared/services/comments.service.spec.ts

@ -71,9 +71,9 @@ describe('CommentsService', () => {
expect(comments!).toEqual(
new CommentsDto(
[
new CommentDto('123', DateTime.parseISO_UTC('2016-10-12T10:10'), 'text1', undefined, user)
new CommentDto('123', DateTime.parseISO('2016-10-12T10:10Z'), 'text1', undefined, user)
], [
new CommentDto('456', DateTime.parseISO_UTC('2017-11-12T12:12'), 'text2', undefined, user)
new CommentDto('456', DateTime.parseISO('2017-11-12T12:12Z'), 'text2', undefined, user)
], [
'789'
],
@ -104,7 +104,7 @@ describe('CommentsService', () => {
user: user
});
expect(comment!).toEqual(new CommentDto('123', DateTime.parseISO_UTC('2016-10-12T10:10'), 'text1', undefined, user));
expect(comment!).toEqual(new CommentDto('123', DateTime.parseISO('2016-10-12T10:10Z'), 'text1', undefined, user));
}));
it('should make put request to replace comment content',

6
frontend/app/shared/services/comments.service.ts

@ -69,7 +69,7 @@ export class CommentsService {
body.createdComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
DateTime.parseISO(item.time),
item.text,
item.url,
item.user);
@ -77,7 +77,7 @@ export class CommentsService {
body.updatedComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
DateTime.parseISO(item.time),
item.text,
item.url,
item.user);
@ -98,7 +98,7 @@ export class CommentsService {
map(body => {
const comment = new CommentDto(
body.id,
DateTime.parseISO_UTC(body.time),
DateTime.parseISO(body.time),
body.text,
body.url,
body.user);

12
frontend/app/shared/services/contents.service.spec.ts

@ -420,15 +420,15 @@ describe('ContentsService', () => {
statusColor: 'black',
newStatus: `NewStatus${id}`,
newStatusColor: 'black',
created: `${id % 1000 + 2000}-12-12T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
createdBy: `creator${id}`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`,
lastModifiedBy: `modifier${id}`,
scheduleJob: {
status: 'Draft',
scheduledBy: `Scheduler${id}`,
color: 'red',
dueTime: `${id % 1000 + 2000}-11-11T10:10:00`
dueTime: `${id % 1000 + 2000}-11-11T10:10:00Z`
},
data: {},
schemaName: 'my-schema',
@ -454,9 +454,9 @@ export function createContent(id: number, suffix = '') {
'black',
`NewStatus${id}${suffix}`,
'black',
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), `creator${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), `modifier${id}`,
new ScheduleDto('Draft', `Scheduler${id}`, 'red', DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`)),
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`), `creator${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
new ScheduleDto('Draft', `Scheduler${id}`, 'red', DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`)),
{},
'my-schema',
'MySchema',

6
frontend/app/shared/services/contents.service.ts

@ -352,8 +352,8 @@ function parseContent(response: any) {
response.statusColor,
response.newStatus,
response.newStatusColor,
DateTime.parseISO_UTC(response.created), response.createdBy,
DateTime.parseISO_UTC(response.lastModified), response.lastModifiedBy,
DateTime.parseISO(response.created), response.createdBy,
DateTime.parseISO(response.lastModified), response.lastModifiedBy,
parseScheduleJob(response.scheduleJob),
response.data,
response.schemaName,
@ -372,5 +372,5 @@ function parseScheduleJob(response: any) {
response.status,
response.scheduledBy,
response.color,
DateTime.parseISO_UTC(response.dueTime));
DateTime.parseISO(response.dueTime));
}

4
frontend/app/shared/services/history.service.spec.ts

@ -68,8 +68,8 @@ describe('HistoryService', () => {
expect(events!).toEqual(
[
new HistoryEventDto('1', 'User1', 'Type 1', 'Message 1', DateTime.parseISO_UTC('2016-12-12T10:10'), new Version('2')),
new HistoryEventDto('2', 'User2', 'Type 2', 'Message 2', DateTime.parseISO_UTC('2016-12-13T10:10'), new Version('3'))
new HistoryEventDto('1', 'User1', 'Type 1', 'Message 1', DateTime.parseISO('2016-12-12T10:10Z'), new Version('2')),
new HistoryEventDto('2', 'User2', 'Type 2', 'Message 2', DateTime.parseISO('2016-12-13T10:10Z'), new Version('3'))
]);
}));
});

2
frontend/app/shared/services/history.service.ts

@ -97,7 +97,7 @@ export class HistoryService {
item.actor,
item.eventType,
item.message,
DateTime.parseISO_UTC(item.created),
DateTime.parseISO(item.created),
new Version(item.version.toString())));
return history;

14
frontend/app/shared/services/rules.service.spec.ts

@ -357,7 +357,7 @@ describe('RulesService', () => {
function ruleEventResponse(id: number, suffix = '') {
return {
id: `id${id}`,
created: `${id % 1000 + 2000}-12-12T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
eventName: `event${id}${suffix}`,
nextAttempt: `${id % 1000 + 2000}-11-11T10:10`,
jobResult: `Failed${id}${suffix}`,
@ -381,7 +381,7 @@ describe('RulesService', () => {
name: `Name${id}${suffix}`,
numSucceeded: id * 3,
numFailed: id * 4,
lastExecuted: `${id % 1000 + 2000}-10-10T10:10:00`,
lastExecuted: `${id % 1000 + 2000}-10-10T10:10:00Z`,
isEnabled: id % 2 === 0,
trigger: {
param1: 1,
@ -407,8 +407,8 @@ export function createRuleEvent(id: number, suffix = '') {
};
return new RuleEventDto(links, `id${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`),
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`),
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`),
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`),
`event${id}${suffix}`,
`url${id}${suffix}`,
`dump${id}${suffix}`,
@ -424,8 +424,8 @@ export function createRule(id: number, suffix = '') {
return new RuleDto(links,
`id${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), `creator${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), `modifier${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`), `creator${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
new Version(`${id}${suffix}`),
id % 2 === 0,
{
@ -443,5 +443,5 @@ export function createRule(id: number, suffix = '') {
`Name${id}${suffix}`,
id * 3,
id * 4,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-10-10T10:10:00`));
DateTime.parseISO(`${id % 1000 + 2000}-10-10T10:10:00Z`));
}

10
frontend/app/shared/services/rules.service.ts

@ -357,8 +357,8 @@ export class RulesService {
const ruleEvents = new RuleEventsDto(body.total, items.map(item =>
new RuleEventDto(item._links,
item.id,
DateTime.parseISO_UTC(item.created),
item.nextAttempt ? DateTime.parseISO_UTC(item.nextAttempt) : null,
DateTime.parseISO(item.created),
item.nextAttempt ? DateTime.parseISO(item.nextAttempt) : null,
item.eventName,
item.description,
item.lastDump,
@ -399,8 +399,8 @@ export class RulesService {
function parseRule(response: any) {
return new RuleDto(response._links,
response.id,
DateTime.parseISO_UTC(response.created), response.createdBy,
DateTime.parseISO_UTC(response.lastModified), response.lastModifiedBy,
DateTime.parseISO(response.created), response.createdBy,
DateTime.parseISO(response.lastModified), response.lastModifiedBy,
new Version(response.version.toString()),
response.isEnabled,
response.trigger,
@ -410,5 +410,5 @@ function parseRule(response: any) {
response.name,
response.numSucceeded,
response.numFailed,
response.lastExecuted ? DateTime.parseISO_UTC(response.lastExecuted) : undefined);
response.lastExecuted ? DateTime.parseISO(response.lastExecuted) : undefined);
}

16
frontend/app/shared/services/schemas.service.spec.ts

@ -596,9 +596,9 @@ describe('SchemasService', () => {
category: `category${id}${suffix}`,
isSingleton: id % 2 === 0,
isPublished: id % 3 === 0,
created: `${id % 1000 + 2000}-12-12T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
createdBy: `creator${id}`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`,
lastModifiedBy: `modifier${id}`,
properties: {
label: `label${id}${suffix}`,
@ -619,9 +619,9 @@ describe('SchemasService', () => {
category: `category${id}${suffix}`,
isSingleton: id % 2 === 0,
isPublished: id % 3 === 0,
created: `${id % 1000 + 2000}-12-12T10:10:00`,
created: `${id % 1000 + 2000}-12-12T10:10:00Z`,
createdBy: `creator${id}`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00`,
lastModified: `${id % 1000 + 2000}-11-11T10:10:00Z`,
lastModifiedBy: `modifier${id}`,
version: `${id}`,
properties: {
@ -808,8 +808,8 @@ export function createSchema(id: number, suffix = '') {
new SchemaPropertiesDto(`label${id}${suffix}`, `hints${id}${suffix}`, [`tags${id}${suffix}`]),
id % 2 === 0,
id % 3 === 0,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), `creator${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), `modifier${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`), `creator${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
new Version(`${id}${suffix}`));
}
@ -825,8 +825,8 @@ export function createSchemaDetails(id: number, suffix = '') {
new SchemaPropertiesDto(`label${id}${suffix}`, `hints${id}${suffix}`, [`tags${id}${suffix}`]),
id % 2 === 0,
id % 3 === 0,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-12-12T10:10:00`), `creator${id}`,
DateTime.parseISO_UTC(`${id % 1000 + 2000}-11-11T10:10:00`), `modifier${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-12-12T10:10:00Z`), `creator${id}`,
DateTime.parseISO(`${id % 1000 + 2000}-11-11T10:10:00Z`), `modifier${id}`,
new Version(`${id}${suffix}`),
[
new RootFieldDto({}, 11, 'field11', createProperties('Array'), 'language', true, true, true, [

8
frontend/app/shared/services/schemas.service.ts

@ -682,8 +682,8 @@ function parseSchemas(response: any) {
new SchemaPropertiesDto(item.properties.label, item.properties.hints, item.properties.tags),
item.isSingleton,
item.isPublished,
DateTime.parseISO_UTC(item.created), item.createdBy,
DateTime.parseISO_UTC(item.lastModified), item.lastModifiedBy,
DateTime.parseISO(item.created), item.createdBy,
DateTime.parseISO(item.lastModified), item.lastModifiedBy,
new Version(item.version.toString())));
const _links = response._links;
@ -703,8 +703,8 @@ function parseSchemaWithDetails(response: any) {
properties,
response.isSingleton,
response.isPublished,
DateTime.parseISO_UTC(response.created), response.createdBy,
DateTime.parseISO_UTC(response.lastModified), response.lastModifiedBy,
DateTime.parseISO(response.created), response.createdBy,
DateTime.parseISO(response.lastModified), response.lastModifiedBy,
new Version(response.version.toString()),
fields,
response.fieldsInLists,

12
frontend/app/shared/services/usages.service.spec.ts

@ -40,7 +40,7 @@ describe('UsagesService', () => {
let usages: CallsUsageDto;
usagesService.getCallsUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
usagesService.getCallsUsages('my-app', '2017-10-12', '2017-10-13').subscribe(result => {
usages = result;
});
@ -75,8 +75,8 @@ describe('UsagesService', () => {
expect(usages!).toEqual(
new CallsUsageDto(100, 1024, 40, 12.4, {
category1: [
new CallsUsagePerDateDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130, 12.3),
new CallsUsagePerDateDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170, 33.3)
new CallsUsagePerDateDto(DateTime.parseISO('2017-10-12'), 10, 130, 12.3),
new CallsUsagePerDateDto(DateTime.parseISO('2017-10-13'), 13, 170, 33.3)
]
})
);
@ -87,7 +87,7 @@ describe('UsagesService', () => {
let usages: ReadonlyArray<StorageUsagePerDateDto>;
usagesService.getStorageUsages('my-app', DateTime.parseISO_UTC('2017-10-12'), DateTime.parseISO_UTC('2017-10-13')).subscribe(result => {
usagesService.getStorageUsages('my-app', '2017-10-12', '2017-10-13').subscribe(result => {
usages = result;
});
@ -111,8 +111,8 @@ describe('UsagesService', () => {
expect(usages!).toEqual(
[
new StorageUsagePerDateDto(DateTime.parseISO_UTC('2017-10-12'), 10, 130),
new StorageUsagePerDateDto(DateTime.parseISO_UTC('2017-10-13'), 13, 170)
new StorageUsagePerDateDto(DateTime.parseISO('2017-10-12'), 10, 130),
new StorageUsagePerDateDto(DateTime.parseISO('2017-10-13'), 13, 170)
]);
}));

12
frontend/app/shared/services/usages.service.ts

@ -82,8 +82,8 @@ export class UsagesService {
pretifyError('Failed to load todays storage size. Please reload.'));
}
public getCallsUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<CallsUsageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`);
public getCallsUsages(app: string, fromDate: string, toDate: string): Observable<CallsUsageDto> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate}/${toDate}`);
return this.http.get<any>(url).pipe(
map(body => {
@ -92,7 +92,7 @@ export class UsagesService {
for (let category of Object.keys(body.details)) {
details[category] = body.details[category].map((item: any) =>
new CallsUsagePerDateDto(
DateTime.parseISO_UTC(item.date),
DateTime.parseISO(item.date),
item.totalBytes,
item.totalCalls,
item.averageElapsedMs));
@ -111,14 +111,14 @@ export class UsagesService {
pretifyError('Failed to load calls usage. Please reload.'));
}
public getStorageUsages(app: string, fromDate: DateTime, toDate: DateTime): Observable<ReadonlyArray<StorageUsagePerDateDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`);
public getStorageUsages(app: string, fromDate: string, toDate: string): Observable<ReadonlyArray<StorageUsagePerDateDto>> {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/${fromDate}/${toDate}`);
return this.http.get<any[]>(url).pipe(
map(body => {
const usages = body.map(item =>
new StorageUsagePerDateDto(
DateTime.parseISO_UTC(item.date),
DateTime.parseISO(item.date),
item.totalCount,
item.totalSize));

8
frontend/app/shared/state/contents.forms.spec.ts

@ -248,7 +248,7 @@ describe('BooleanField', () => {
});
describe('DateTimeField', () => {
const now = DateTime.parseISO_UTC('2017-10-12T16:30:10Z');
const now = DateTime.parseISO('2017-10-12T16:30:10Z');
const field = createField({ properties: createProperties('DateTime', { editor: 'DateTime', isRequired: true }) });
it('should create validators', () => {
@ -263,6 +263,12 @@ describe('DateTimeField', () => {
expect(FieldFormatter.format(field, true)).toBe(true);
});
it('should format old format to date', () => {
const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });
expect(FieldFormatter.format(dateField, '2017-12-12')).toBe('2017-12-12');
});
it('should format to date', () => {
const dateField = createField({ properties: createProperties('DateTime', { editor: 'Date' }) });

10
frontend/app/shared/state/contents.forms.ts

@ -159,12 +159,12 @@ export class FieldFormatter implements FieldPropertiesVisitor<FieldValue> {
public visitDateTime(properties: DateTimeFieldPropertiesDto): FieldValue {
try {
const parsed = DateTime.parseISO_UTC(this.value);
const parsed = DateTime.parseISO(this.value);
if (properties.editor === 'Date') {
return parsed.toUTCStringFormat('YYYY-MM-DD');
return parsed.toStringFormatUTC('yyyy-MM-dd');
} else {
return parsed.toUTCStringFormat('YYYY-MM-DD HH:mm:ss');
return parsed.toStringFormatUTC('yyyy-MM-dd HH:mm:ss');
}
} catch (ex) {
return this.value;
@ -388,9 +388,9 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
const now = this.now || DateTime.now();
if (properties.calculatedDefaultValue === 'Now') {
return `${now.toUTCStringFormat('YYYY-MM-DDTHH:mm:ss')}Z`;
return `${now.toStringFormatUTC('yyyy-MM-dd\'T\'HH:mm:ss')}Z`;
} else if (properties.calculatedDefaultValue === 'Today') {
return `${now.toUTCStringFormat('YYYY-MM-DD')}T00:00:00Z`;
return `${now.toISODate()}T00:00:00Z`;
} else {
return properties.defaultValue;
}

11
frontend/package-lock.json

@ -492,9 +492,9 @@
}
},
"@types/jasmine": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.5.tgz",
"integrity": "sha512-LlhwGivHkUV8ehNmaXjGGXopLm91G9ORIRcjw7Ya47jVAIGudewFZM2PdPXBvueZfRWwYzLt083wiPfKRXrSlg==",
"version": "3.5.9",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.9.tgz",
"integrity": "sha512-KNL2Fq6GRmty2j6+ZmueT/Z/dkctLNH+5DFoGHNDtcgt7yME9NZd8x2p81Yuea1Xux/qAryDd3zVLUoKpDz1TA==",
"dev": true
},
"@types/jquery": {
@ -3127,6 +3127,11 @@
"assert-plus": "^1.0.0"
}
},
"date-fns": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.10.0.tgz",
"integrity": "sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA=="
},
"date-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",

2
frontend/package.json

@ -31,12 +31,12 @@
"bootstrap": "4.4.1",
"core-js": "3.6.4",
"cropperjs": "2.0.0-alpha.1",
"date-fns": "^2.10.0",
"graphiql": "0.17.5",
"graphql": "14.6.0",
"image-focus": "^1.1.0",
"marked": "0.8.0",
"mersenne-twister": "1.1.0",
"moment": "2.24.0",
"mousetrap": "1.6.5",
"ngx-color-picker": "9.0.0",
"oidc-client": "1.10.1",

Loading…
Cancel
Save