Handle invalid project templates

This commit is contained in:
Adina Țeudan 2021-04-29 01:00:26 +03:00
parent 0757a468bd
commit ec07903562
13 changed files with 160 additions and 76 deletions

View File

@ -11,14 +11,12 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { languageInitializer } from './i18n/language.initializer';
import { LanguageService } from './i18n/language.service';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { ToastrModule } from 'ngx-toastr';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { AuthModule } from './modules/auth/auth.module';
import { LogoComponent } from './components/logo/logo.component';
import { AuthErrorComponent } from './components/auth-error/auth-error.component';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { ToastComponent } from './components/toast/toast.component';
import { HttpCacheInterceptor } from '@redaction/red-cache';
import { NotificationsComponent } from './components/notifications/notifications.component';
@ -97,18 +95,6 @@ const components = [AppComponent, LogoComponent, AuthErrorComponent, ToastCompon
multi: true,
useFactory: languageInitializer,
deps: [LanguageService]
},
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
{
provide: MAT_DATE_FORMATS,
useValue: {
display: {
dateInput: 'DD/MM/YY',
monthYearLabel: 'YYYY',
dateA11yLabel: 'LL',
monthYearA11yLabel: 'YYYY'
}
}
}
],
bootstrap: [AppComponent]

View File

@ -43,30 +43,38 @@
></textarea>
</div>
<div class="valid-from">
<mat-checkbox [checked]="hasValidFrom" (change)="hasValidFrom = !hasValidFrom" class="filter-menu-checkbox" color="primary">
{{ 'project-listing.add-edit-dialog.form.due-date' | translate }}
</mat-checkbox>
<div class="validity">
<div>
<mat-checkbox [checked]="hasValidFrom" (change)="hasValidFrom = !hasValidFrom" class="filter-menu-checkbox" color="primary">
{{ 'add-edit-project-template.form.valid-from' | translate }}
</mat-checkbox>
<ng-container *ngIf="hasValidFrom">
<div class="red-input-group datepicker-wrapper ml-16 mr-16">
<input placeholder="dd/mm/yy" [matDatepicker]="fromPicker" formControlName="validFrom" />
<mat-datepicker-toggle matSuffix [for]="fromPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
<mat-checkbox [checked]="hasValidTo" (change)="hasValidTo = !hasValidTo" class="filter-menu-checkbox" color="primary">
{{ 'add-edit-project-template.form.valid-to' | translate }}
</mat-checkbox>
</div>
<div>
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidFrom">
<input placeholder="dd/mm/yy" [matDatepicker]="fromPicker" formControlName="validFrom" />
<mat-datepicker-toggle matSuffix [for]="fromPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</ng-container>
</div>
to
<div class="red-input-group datepicker-wrapper ml-16">
<input placeholder="dd/mm/yy" [matDatepicker]="toPicker" formControlName="validTo" />
<mat-datepicker-toggle matSuffix [for]="toPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
<div class="red-input-group datepicker-wrapper">
<ng-container *ngIf="hasValidTo">
<input placeholder="dd/mm/yy" [matDatepicker]="toPicker" formControlName="validTo" />
<mat-datepicker-toggle matSuffix [for]="toPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</ng-container>
</div>
</ng-container>
</div>
</div>
</div>

View File

@ -1,19 +1,23 @@
.valid-from {
margin-top: 16px;
min-height: 34px;
.validity {
width: 230px;
display: flex;
flex-direction: row;
align-items: center;
mat-checkbox {
width: fit-content;
}
> div {
display: flex;
flex-direction: column;
margin-top: 16px;
.mr-16 {
margin-right: 16px;
}
mat-checkbox {
margin-right: 16px;
height: 100%;
align-items: center;
display: flex;
min-height: 42px;
}
.ml-16 {
margin-left: 16px;
.red-input-group {
min-height: 42px;
justify-content: center;
}
}
}

View File

@ -3,7 +3,9 @@ import { AppStateService } from '../../../../state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as moment from 'moment';
import { Moment } from 'moment';
import { RuleSetControllerService, RuleSetModel } from '@redaction/red-ui-http';
import { applyIntervalConstraints } from '../../../../utils/date-inputs-utils';
@Component({
selector: 'redaction-add-edit-rule-set-dialog',
@ -13,9 +15,13 @@ import { RuleSetControllerService, RuleSetModel } from '@redaction/red-ui-http';
export class AddEditRuleSetDialogComponent {
public ruleSetForm: FormGroup;
public hasValidFrom: boolean;
public hasValidTo: boolean;
public downloadTypesEnum = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
public reportTypesEnum = Object.values(RuleSetModel.ReportTypesEnum);
private _previousValidFrom: Moment;
private _previousValidTo: Moment;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
@ -26,8 +32,8 @@ export class AddEditRuleSetDialogComponent {
this.ruleSetForm = this._formBuilder.group({
name: [this.ruleSet?.name, Validators.required],
description: [this.ruleSet?.description],
validFrom: [this.ruleSet?.validFrom],
validTo: [this.ruleSet?.validTo],
validFrom: [this.ruleSet?.validFrom ? moment(this.ruleSet?.validFrom) : null, this._requiredIfValidator(() => this.hasValidFrom)],
validTo: [this.ruleSet?.validTo ? moment(this.ruleSet?.validTo) : null, this._requiredIfValidator(() => this.hasValidTo)],
downloadFileTypes: [this.ruleSet ? this.ruleSet.downloadFileTypes : ['PREVIEW', 'REDACTED']],
reportTypes: [
this.ruleSet
@ -36,18 +42,55 @@ export class AddEditRuleSetDialogComponent {
Validators.required
]
});
this.hasValidFrom = !!this.ruleSet?.validFrom && !!this.ruleSet?.validTo;
this.hasValidFrom = !!this.ruleSet?.validFrom;
this.hasValidTo = !!this.ruleSet?.validTo;
this._previousValidFrom = this.ruleSetForm.get('validFrom').value;
this._previousValidTo = this.ruleSetForm.get('validTo').value;
this.ruleSetForm.valueChanges.subscribe((value) => {
this._applyValidityIntervalConstraints(value);
});
}
private _applyValidityIntervalConstraints(value): boolean {
if (applyIntervalConstraints(value, this._previousValidFrom, this._previousValidTo, this.ruleSetForm, 'validFrom', 'validTo')) {
return true;
}
this._previousValidFrom = this.ruleSetForm.get('validFrom').value;
this._previousValidTo = this.ruleSetForm.get('validTo').value;
return false;
}
private _requiredIfValidator(predicate) {
return (formControl) => {
if (!formControl.parent) {
return null;
}
if (predicate()) {
return Validators.required(formControl);
}
return null;
};
}
public get changed(): boolean {
if (!this.ruleSet) return true;
for (const key of Object.keys(this.ruleSetForm.getRawValue())) {
if (key === 'validFrom' || key === 'validTo') {
if (this.hasValidFrom !== (!!this.ruleSet.validFrom && !!this.ruleSet.validTo)) {
if (key === 'validFrom') {
if (this.hasValidFrom !== !!this.ruleSet.validFrom) {
return true;
}
if (this.hasValidFrom && !moment(this.ruleSet[key]).isSame(moment(this.ruleSetForm.get(key).value))) {
if (this.hasValidFrom && !moment(this.ruleSet.validFrom).isSame(moment(this.ruleSetForm.get('validFrom').value))) {
return true;
}
} else if (key === 'validTo') {
if (this.hasValidTo !== !!this.ruleSet.validTo) {
return true;
}
if (this.hasValidTo && !moment(this.ruleSet.validTo).isSame(moment(this.ruleSetForm.get('validTo').value))) {
return true;
}
} else if (this.ruleSet[key] !== this.ruleSetForm.get(key).value) {
@ -65,7 +108,9 @@ export class AddEditRuleSetDialogComponent {
async saveRuleSet() {
const ruleSet = {
ruleSetId: this.ruleSet?.ruleSetId,
...this.ruleSetForm.getRawValue()
...this.ruleSetForm.getRawValue(),
validFrom: this.hasValidFrom ? this.ruleSetForm.get('validFrom').value : null,
validTo: this.hasValidTo ? this.ruleSetForm.get('validTo').value : null
};
await this._ruleSetController.createOrUpdateRuleSet(ruleSet).toPromise();
await this._appStateService.loadAllRuleSets();

View File

@ -97,7 +97,7 @@
<redaction-empty-state *ngIf="!logs?.totalHits" icon="red:document" screen="audit-screen"></redaction-empty-state>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<div class="table-item pointer" *cdkVirtualFor="let log of logs?.data">
<div class="table-item" *cdkVirtualFor="let log of logs?.data">
<div>
{{ log.message }}
</div>

View File

@ -4,6 +4,7 @@ import { FormBuilder, FormGroup } from '@angular/forms';
import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http';
import { TranslateService } from '@ngx-translate/core';
import { Moment } from 'moment';
import { applyIntervalConstraints } from '../../../../utils/date-inputs-utils';
const PAGE_SIZE = 50;
@ -49,19 +50,10 @@ export class AuditScreenComponent {
}
private _updateDateFilters(value): boolean {
if (!!value.to && !!value.from) {
if (this._previousFrom !== value.from) {
if (value.to.isBefore(value.from)) {
this.filterForm.patchValue({ to: value.from });
return true;
}
} else if (this._previousTo !== value.to) {
if (value.to.isBefore(value.from)) {
this.filterForm.patchValue({ from: value.to });
return true;
}
}
if (applyIntervalConstraints(value, this._previousFrom, this._previousTo, this.filterForm, 'from', 'to')) {
return true;
}
this._previousFrom = this.filterForm.get('from').value;
this._previousTo = this.filterForm.get('to').value;
return false;

View File

@ -55,10 +55,6 @@
margin-top: 12px;
}
.mt-16 {
margin-top: 16px;
}
.mt-24 {
margin-top: 24px;
}

View File

@ -19,7 +19,7 @@
<div class="red-input-group required w-400">
<mat-form-field floatLabel="always">
<mat-label>{{ 'project-listing.add-edit-dialog.form.template' | translate }}</mat-label>
<mat-select formControlName="ruleSet" style="width: 100%;" (valueChange)="ruleSetChanged($event)">
<mat-select formControlName="ruleSetId" style="width: 100%;" (valueChange)="ruleSetChanged($event)">
<mat-option
*ngFor="let ruleSet of ruleSets"
[value]="ruleSet.ruleSetId"

View File

@ -16,6 +16,7 @@ export class AddEditProjectDialogComponent {
public hasDueDate: boolean;
public downloadTypesEnum = ['ORIGINAL', 'PREVIEW', 'REDACTED'];
public reportTypesEnum = Object.values(RuleSetModel.ReportTypesEnum);
public ruleSets: RuleSetModel[];
constructor(
private readonly _appStateService: AppStateService,
@ -23,9 +24,10 @@ export class AddEditProjectDialogComponent {
public dialogRef: MatDialogRef<AddEditProjectDialogComponent>,
@Inject(MAT_DIALOG_DATA) public project: ProjectWrapper
) {
this._filterInvalidRuleSets();
this.projectForm = this._formBuilder.group({
projectName: [this.project?.projectName, Validators.required],
ruleSet: [{ value: this.project?.ruleSetId, disabled: this.project?.hasFiles }, Validators.required],
ruleSetId: [{ value: this.project?.ruleSetId, disabled: this.project?.hasFiles }, Validators.required],
downloadFileTypes: [this.project?.project?.downloadFileTypes],
reportTypes: [this.project?.project?.reportTypes, Validators.required],
description: [this.project?.description],
@ -34,8 +36,15 @@ export class AddEditProjectDialogComponent {
this.hasDueDate = !!this.project?.dueDate;
}
public get ruleSets(): RuleSetModel[] {
return this._appStateService.ruleSets;
private _filterInvalidRuleSets() {
this.ruleSets = this._appStateService.ruleSets.filter((r) => {
if (this.project?.ruleSetId === r.ruleSetId) {
return true;
}
const notYetValid = !!r.validFrom && moment(r.validFrom).isAfter(moment());
const notValidAnymore = !!r.validTo && moment(r.validTo).add(1, 'd').isBefore(moment());
return !(notYetValid || notValidAnymore);
});
}
public humanize(value: string): string {
@ -55,6 +64,10 @@ export class AddEditProjectDialogComponent {
if (this.hasDueDate && !moment(this.project.dueDate).isSame(moment(this.projectForm.get(key).value))) {
return true;
}
} else if (key === 'downloadFileTypes' || key === 'reportTypes') {
if (this.project.project[key] !== this.projectForm.get(key).value) {
return true;
}
} else if (this.project[key] !== this.projectForm.get(key).value) {
return true;
}

View File

@ -29,6 +29,8 @@ import { EmptyStateComponent } from './components/empty-state/empty-state.compon
import { BaseListingComponent } from './base/base-listing.component';
import { SortByPipe } from './components/sort-pipe/sort-by.pipe';
import { RoundCheckboxComponent } from './components/checkbox/round-checkbox.component';
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
import { MomentDateAdapter } from '@angular/material-moment-adapter';
const buttons = [ChevronButtonComponent, CircleButtonComponent, FileDownloadBtnComponent, IconButtonComponent, UserButtonComponent];
@ -60,6 +62,20 @@ const modules = [MatConfigModule, TranslateModule, ScrollingModule, IconsModule,
@NgModule({
declarations: [...components, ...utils],
imports: [CommonModule, ...modules],
exports: [...modules, ...components, ...utils, RoundCheckboxComponent]
exports: [...modules, ...components, ...utils, RoundCheckboxComponent],
providers: [
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
{
provide: MAT_DATE_FORMATS,
useValue: {
display: {
dateInput: 'DD/MM/YY',
monthYearLabel: 'YYYY',
dateA11yLabel: 'LL',
monthYearA11yLabel: 'YYYY'
}
}
}
]
})
export class SharedModule {}

View File

@ -0,0 +1,19 @@
import { FormGroup } from '@angular/forms';
import { Moment } from 'moment';
export function applyIntervalConstraints(value, previousFrom: Moment, previousTo: Moment, form: FormGroup, fromKey: string, toKey: string): boolean {
if (!!value[fromKey] && !!value[toKey]) {
if (previousFrom !== value[fromKey]) {
if (value[toKey].isBefore(value[fromKey])) {
form.patchValue({ [toKey]: value[fromKey] });
return true;
}
} else if (previousTo !== value[toKey]) {
if (value[toKey].isBefore(value[fromKey])) {
form.patchValue({ [fromKey]: value[toKey] });
return true;
}
}
}
return false;
}

View File

@ -690,6 +690,7 @@
"description": "Description",
"description-placeholder": "Enter Description",
"valid-from": "Valid from",
"valid-to": "Valid to",
"download-file-types": {
"label": "Download file types"
},

View File

@ -228,6 +228,10 @@ section.settings {
margin-top: 8px;
}
.mt-16 {
margin-top: 16px;
}
.mt-20 {
margin-top: 20px;
}