Commit a48ee2af authored by Christopher Keim's avatar Christopher Keim
Browse files

[TOB-127,21,406,74,315,52,92,354,391] feat: v0.9.0



[TOB-127] feat: Add components to display statement details

* Add back end calls for statement details
* Add component to display general statement information
* Add component to display geographic position
* Add component to display contribution status
* Add component to display statements inbox attachments
* Add component to display statements outbox attachments
* Add component to display linked statements
* Integrate details components in details page
* Integrate details components in form components
* Refresh process history on task change

[TOB-21] feat: Add functionality to manually redispatch statement email

* Add back end calls to redispatch statement email
* Add store actions and effect for redispatching statement email
* Integrate store into statement details page

[TOB-406] feat: Add statement search

* Extend back end API interface for search parameters
* Add component for search filters
* Add sorting buttons to statement table component
* Integrate statement search into search page

[TOB-74] feat: Add map to position search page

* Reorganize routing of search subpages
* Add directive to display leaflet popups
* Integrate leaflet map, markers and popups into position search page

[TOB-315] feat: Add search functionality to position search page

* Add back end calls for position search
* Add store effect for position search
* Integrate search component and store into position search page

[TOB-52] feat: Add GIS call to leaflet map

* Reorganize website configuration to separate config file
* Add back end calls to transform geographic positions
* Add store module for leaflet map
* Add store effecto to open GIS
* Integrate store module into map components
* Match leaflet styling to openk theme

[TOB-92] feat: Add upload functionality for consideration attachments

* Add component to display and upload consideration attachments
* Integrate component into details page

[TOB-354] feat: Add dockerfiles

[TOB-391] fix: Fix minor bugs

* Cancel rerouting of email when leaving mail page
* Prevent refetching of deleted emails
* Properly reset error messages in side menu
* Also perform contact search in new statement page when no mailid is provided
* Close drop downs on scroll
* Fix minor styling issues
Signed-off-by: Christopher Keim's avatarChristopher Keim <keim@develop-group.de>
parent c14146b0
......@@ -57,14 +57,14 @@
<button (click)="toggleTag(tag?.id, appValue?.tagIds?.indexOf(tag?.id) > - 1)"
*ngIf="!appHideNotUsedTags || appValue?.tagIds?.length > 0 && appValue.tagIds.indexOf(tag?.id) > -1"
[class.openk-info]="appValue?.tagIds?.length > 0 && appValue.tagIds.indexOf(tag?.id) > -1"
[disabled]="appDisabled"
[disabled]="appDisabled || !appEditableTags"
class="openk-button openk-chip">
{{tag?.label}}
</button>
</ng-container>
<button (click)="appHideNotUsedTags = !appHideNotUsedTags"
*ngIf="appTagList?.length > 0"
*ngIf="appTagList?.length > 0 && appEditableTags"
[class.tag-toggle---plus]="appHideNotUsedTags"
[disabled]="appDisabled"
class="openk-button openk-button-rounded tag-toggle"
......
......@@ -58,6 +58,9 @@ export class AttachmentControlComponent extends AbstractControlValueAccessorComp
@Input()
public appHideNotUsedTags: boolean;
@Input()
public appEditableTags = true;
public toggleSelection(isSelected: boolean) {
if (this.appDisabled) {
return;
......
<!-------------------------------------------------------------------------------
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
-------------------------------------------------------------------------------->
<span class="title">
{{appTitle}}
</span>
<div class="attachment-list">
<app-attachment-control
(appDownloadAttachment)="appDownload.emit(att?.id)"
*ngFor="let att of appAttachments"
[appEditableTags]="false"
[appHideNotUsedTags]="true"
[appIsDownloadable]="true"
[appIsSelectable]="false"
[appTagList]="appTagList"
[appValue]="att"
class="attachment-list--control">
</app-attachment-control>
<span *ngIf="appAttachments?.length <= 0"
class="attachment-list--placeholder">{{"details.attachments.noResult" | translate}}</span>
</div>
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
@import "openk.styles";
.title {
font-weight: 600;
}
.attachment-list {
margin-top: 0.25em;
}
.attachment-list--placeholder {
font-size: smaller;
}
.attachment-list--control {
box-sizing: border-box;
width: fit-content;
}
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {async, ComponentFixture, TestBed} from "@angular/core/testing";
import {I18nModule} from "../../../../../core/i18n";
import {AttachmentsFormModule} from "../../attachments-form.module";
import {AttachmentDisplayListComponent} from "./attachment-display-list.component";
describe("AttachmentDisplayListComponent", () => {
let component: AttachmentDisplayListComponent;
let fixture: ComponentFixture<AttachmentDisplayListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
I18nModule,
AttachmentsFormModule
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AttachmentDisplayListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {withKnobs} from "@storybook/addon-knobs";
import {moduleMetadata, storiesOf} from "@storybook/angular";
import {I18nModule} from "../../../../../core/i18n";
import {AttachmentsFormModule} from "../../attachments-form.module";
storiesOf("Features / Forms / Attachments", module)
.addDecorator(withKnobs)
.addDecorator(moduleMetadata({
imports: [
I18nModule,
AttachmentsFormModule
]
}))
.add("AttachmentDisplayListComponent", () => ({
template: `
<app-attachment-display-list style="margin: 1em; box-sizing: border-box">
</app-attachment-display-list>
`,
props: {}
}));
......@@ -11,29 +11,27 @@
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {CommonModule} from "@angular/common";
import {NgModule} from "@angular/core";
import {MatIconModule} from "@angular/material/icon";
import {TranslateModule} from "@ngx-translate/core";
import {SearchbarModule} from "../layout/searchbar";
import {StatementTableModule} from "../layout/statement-table";
import {LinkedStatementsComponent} from "./linked-statements";
import {Component, EventEmitter, Input, Output} from "@angular/core";
import {IAPIAttachmentTag} from "../../../../../core/api/attachments";
import {IAttachmentControlValue} from "../../../../../store/attachments/model";
@NgModule({
imports: [
CommonModule,
MatIconModule,
TranslateModule,
StatementTableModule,
SearchbarModule
],
declarations: [
LinkedStatementsComponent,
],
exports: [
LinkedStatementsComponent
]
@Component({
selector: "app-attachment-display-list",
templateUrl: "./attachment-display-list.component.html",
styleUrls: ["./attachment-display-list.component.scss"]
})
export class LinkedStatementsModule {
export class AttachmentDisplayListComponent {
@Input()
public appTitle: string;
@Input()
public appAttachments: IAttachmentControlValue[] = [];
@Input()
public appTagList: IAPIAttachmentTag[] = [];
@Output()
public appDownload = new EventEmitter<number>();
}
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
export * from "./attachment-display-list.component";
......@@ -13,7 +13,7 @@
<ng-container [formGroup]="appFormGroup">
<label>
<label class="file-drop--title">
{{appTitle}}
</label>
......@@ -32,17 +32,19 @@
[appTagList]="appTagList"
[formControlName]="i"
class="file-drop--control">
</app-attachment-control>
</app-file-drop>
<button (click)="fileDropComponent.openDialog()"
[disabled]="fileDropComponent.appDisabled"
class="openk-button attachments--select-file-button">
<mat-icon>attach_file</mat-icon>
{{"attachments.selectFile" | translate }}
</button>
<div class="attachments--select-file-button">
<button (click)="fileDropComponent.openDialog()"
[disabled]="fileDropComponent.appDisabled"
class="openk-button">
<mat-icon class="attachments--select-file-button--icon">attach_file</mat-icon>
{{"attachments.selectFile" | translate }}
</button>
<ng-content></ng-content>
</div>
</ng-container>
......@@ -23,6 +23,10 @@
margin: 0.25em 0 0.5em 0;
}
.file-drop--title {
font-weight: 600;
}
.file-drop--control {
font-size: small;
width: 100%;
......@@ -40,3 +44,9 @@
.attachments--select-file-button {
margin-left: auto;
}
.attachments--select-file-button--icon {
height: initial;
width: initial;
font-size: 1em;
}
......@@ -37,7 +37,7 @@ export class AttachmentFileDropFormComponent extends AbstractReactiveFormArrayCo
public appFormGroup = createAttachmentForm();
@Input()
public appFormArrayName: keyof IAttachmentFormValue = "add";
public appFormArrayName: keyof IAttachmentFormValue | "considerations" = "add";
public addControlForFiles(files: File[]) {
arrayJoin(files).forEach((file) => {
......
......@@ -23,19 +23,19 @@ $break-point: 16em;
.attachments {
display: flex;
flex-direction: row;
padding: 1em;
}
.attachments--files {
padding: 0.5em;
box-sizing: border-box;
overflow: auto;
flex: 1;
}
.attachments--email {
padding: 0.5em;
box-sizing: border-box;
flex: 1;
margin-right: 0.5em;
}
.attachments---half-size {
......
......@@ -11,10 +11,10 @@
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges} from "@angular/core";
import {Component, Input, OnDestroy, OnInit} from "@angular/core";
import {select, Store} from "@ngrx/store";
import {BehaviorSubject, combineLatest} from "rxjs";
import {delay, filter, switchMap, take, takeUntil} from "rxjs/operators";
import {combineLatest, Observable} from "rxjs";
import {delay, filter, take, takeUntil} from "rxjs/operators";
import {AUTO_SELECTED_TAGS, IAPIAttachmentModel} from "../../../../core/api/attachments";
import {IAPIEmailAttachmentModel, IAPIEmailModel} from "../../../../core/api/mail";
import {
......@@ -25,6 +25,7 @@ import {
getAttachmentControlValueSelector,
getFilteredAttachmentTagsSelector,
getStatementAttachmentCacheSelector,
IAttachmentControlValue,
IAttachmentFormValue,
queryParamsIdSelector,
startAttachmentDownloadAction
......@@ -33,6 +34,7 @@ import {downloadEmailAttachmentAction} from "../../../../store/mail/actions";
import {getSelectedEmailSelector, getStatementMailSelector} from "../../../../store/mail/selectors";
import {arrayJoin} from "../../../../util/store";
import {AbstractReactiveFormComponent} from "../../abstract";
import {getMailAttachment, getMailAttachments} from "../util/mail-attachments.util";
@Component({
selector: "app-attachments-form-group",
......@@ -41,16 +43,16 @@ import {AbstractReactiveFormComponent} from "../../abstract";
})
export class AttachmentsFormGroupComponent
extends AbstractReactiveFormComponent<IAttachmentFormValue>
implements OnInit, OnChanges, OnDestroy {
implements OnInit, OnDestroy {
@Input()
public appAutoTagIds: string[];
@Input()
public appForbiddenTagIds: string[];
public appForbiddenTagIds: string[] = [];
@Input()
public appRestrictedTagIds: string[];
public appRestrictedTagIds: string[] = [];
@Input()
public appWithoutTagControl: boolean;
......@@ -78,7 +80,7 @@ export class AttachmentsFormGroupComponent
public statementMail$ = this.store.pipe(select(getStatementMailSelector));
public attachments$ = this.store.pipe(select(getAttachmentControlValueSelector, {}));
public attachments$: Observable<IAttachmentControlValue[]>;
public allAttachments$ = this.store.pipe(select(getAllStatementAttachments));
......@@ -86,20 +88,15 @@ export class AttachmentsFormGroupComponent
public tagList$ = this.store.pipe(select(getFilteredAttachmentTagsSelector, {without: AUTO_SELECTED_TAGS}));
private attachmentProps$ = new BehaviorSubject<{ restrictedTagIds: string[], forbiddenTagIds: string[] }>({
restrictedTagIds: [],
forbiddenTagIds: []
});
public constructor(public store: Store) {
super();
}
public ngOnInit() {
this.attachments$ = this.attachmentProps$.pipe(
switchMap((props) => this.store.pipe(
select(getAttachmentControlValueSelector, props)
))
this.attachments$ = this.store.pipe(
select(getAttachmentControlValueSelector,
{forbiddenTagIds: this.appForbiddenTagIds, restrictedTagIds: this.appRestrictedTagIds})
);
this.attachments$.pipe(delay(0), takeUntil(this.destroy$))
......@@ -121,16 +118,6 @@ export class AttachmentsFormGroupComponent
this.store.dispatch(fetchAttachmentTagsAction());
}
public ngOnChanges(changes: SimpleChanges) {
const keys: Array<keyof AttachmentsFormGroupComponent> = ["appRestrictedTagIds", "appForbiddenTagIds"];
if (keys.some((_) => changes[_] != null)) {
this.attachmentProps$.next({
restrictedTagIds: this.appRestrictedTagIds,
forbiddenTagIds: this.appForbiddenTagIds
});
}
}
public ngOnDestroy() {
this.statementId$.pipe(take(1))
.subscribe((statementId) => this.store.dispatch(clearAttachmentCacheAction({statementId})));
......@@ -151,28 +138,13 @@ export class AttachmentsFormGroupComponent
}
public setMailTextAttachmentValue(attachments: IAPIAttachmentModel[], isNewStatement: boolean) {
const mailTextAttachmentId = attachments.find((_) =>
_.name === "mailText.txt" && _.tagIds && _.tagIds.length === 2
&& _.tagIds[0] === "email" && _.tagIds[1] === "email-text")?.id;
const mailTextAttachmentId = getMailAttachment(attachments)?.id;
this.appFormGroup.patchValue({mailTextAttachmentId, transferMailText: mailTextAttachmentId != null || isNewStatement});
}
public setMailAttachmentValues(attachments: IAPIAttachmentModel[], emailAttachments: IAPIEmailAttachmentModel[]) {
const mergedAttachmentLists = emailAttachments.map((emailAttachment) => {
const emailAttachmentFromAttachmentsArray = attachments.find((attachment) =>
attachment.name === emailAttachment.name && attachment.tagIds.find((_) => _ === "email") != null);
const isSelected = emailAttachmentFromAttachmentsArray != null || this.appForNewStatement;
const tagIds = emailAttachmentFromAttachmentsArray?.tagIds;
return {
...(emailAttachmentFromAttachmentsArray ? emailAttachmentFromAttachmentsArray : emailAttachment),
isSelected,
tagIds: arrayJoin(tagIds)
};
});
const mergedAttachmentLists = getMailAttachments(attachments, emailAttachments, this.appForNewStatement);
this.setValueForArray(mergedAttachmentLists, "email");
}
......
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {IAPIAttachmentModel} from "../../../../core/api/attachments";
import {IAPIEmailModel} from "../../../../core/api/mail";
import {GetEmailTextAttachmentPipe} from "./get-email-text-attachment.pipe";
describe("GetEmailTextAttachmentPipe", () => {
const pipe = new GetEmailTextAttachmentPipe();
describe("transform", () => {
it("should return the mail text attachment with name set to mail subject", () => {
const mail: IAPIEmailModel = {
subject: "subject",
from: "from"
} as IAPIEmailModel;
const attachments: IAPIAttachmentModel[] = [
{name: "attachment1", tagIds: []},
{name: "attachment2", tagIds: []},
{name: "mailText.txt", tagIds: ["email", "email-text"]}
] as IAPIAttachmentModel[];
const mailTextAttachment = pipe.transform(attachments, mail);
expect(mailTextAttachment).toEqual({...attachments[2], name: `"${mail.subject}" (${mail.from})`});
});
it("should return null if no mail text attachment was found", () => {
let mailTextAttachment = pipe.transform(null, null);
expect(mailTextAttachment).toEqual(null);
mailTextAttachment = pipe.transform([], null);
expect(mailTextAttachment).toEqual(null);
});
});
});
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {Pipe, PipeTransform} from "@angular/core";
import {IAPIAttachmentModel} from "../../../../core/api/attachments";
import {IAPIEmailModel} from "../../../../core/api/mail";
import {getMailAttachment} from "../util/mail-attachments.util";
@Pipe({
name: "appGetEmailTextAttachment"
})
export class GetEmailTextAttachmentPipe implements PipeTransform {
public transform(attachments: IAPIAttachmentModel[], mail: IAPIEmailModel): IAPIAttachmentModel {
const mailTextAttachment = getMailAttachment(attachments);
if (!mailTextAttachment) {
return null;
}
const name = mail ? `"${mail.subject}" (${mail.from})` : mailTextAttachment.name;
return {
...mailTextAttachment,
name
};
}
}
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0