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
/********************************************************************************
* 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 {Store} from "@ngrx/store";
import {provideMockStore} from "@ngrx/store/testing";
import {I18nModule} from "../../../../core/i18n";
import {startAttachmentDownloadAction} from "../../../../store/attachments/actions";
import {IAttachmentControlValue} from "../../../../store/attachments/model";
import {setErrorAction} from "../../../../store/root/actions";
import {queryParamsIdSelector} from "../../../../store/root/selectors";
import {submitConsiderationFilesAction} from "../../../../store/statements/actions";
import {StatementDetailsModule} from "../../statement-details.module";
import {StatementDetailsConsiderationsComponent} from "./statement-details-considerations.component";
describe("StatementDetailsConsiderationsComponent", () => {
let component: StatementDetailsConsiderationsComponent;
let fixture: ComponentFixture<StatementDetailsConsiderationsComponent>;
let store: Store;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StatementDetailsModule,
I18nModule
],
providers: [
provideMockStore({
initialState: {},
selectors: [
{
selector: queryParamsIdSelector,
value: 19
}
]
})
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatementDetailsConsiderationsComponent);
component = fixture.componentInstance;
store = fixture.componentRef.injector.get(Store);
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should dispatch submitConsiderationFilesAction on submitting with the current files from cache", async () => {
const dispatchSpy = spyOn(store, "dispatch");
const file: IAttachmentControlValue = {name: "file1", tagIds: []};
component.setValueForArray([file], "considerations");
await component.submit();
expect(dispatchSpy).toHaveBeenCalledWith(setErrorAction({statementId: 19, error: null}));
expect(dispatchSpy).toHaveBeenCalledWith(submitConsiderationFilesAction({
statementId: 19, value: [file]
}));
});
it("should dispatch startAttachmentDownloadAction with correct attachmentId", async () => {
const dispatchSpy = spyOn(store, "dispatch");
await component.downloadAttachment(15);
expect(dispatchSpy).toHaveBeenCalledWith(startAttachmentDownloadAction({statementId: 19, attachmentId: 15}));
});
});
/********************************************************************************
* 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 {Component, Input, OnDestroy, OnInit} from "@angular/core";
import {FormArray} from "@angular/forms";
import {select, Store} from "@ngrx/store";
import {delay, take, takeUntil} from "rxjs/operators";
import {startAttachmentDownloadAction} from "../../../../store/attachments/actions";
import {IAttachmentControlValue} from "../../../../store/attachments/model";
import {getConsiderationAttachments, getStatementAttachmentCacheSelector} from "../../../../store/attachments/selectors";
import {setErrorAction} from "../../../../store/root/actions";
import {isOfficialInChargeSelector, queryParamsIdSelector} from "../../../../store/root/selectors";
import {submitConsiderationFilesAction} from "../../../../store/statements/actions";
import {statementLoadingSelector} from "../../../../store/statements/selectors";
import {createFormGroup} from "../../../../util/forms";
import {AbstractReactiveFormComponent} from "../../../forms/abstract";
@Component({
selector: "app-statement-details-considerations",
templateUrl: "./statement-details-considerations.component.html",
styleUrls: ["./statement-details-considerations.component.scss"]
})
export class StatementDetailsConsiderationsComponent
extends AbstractReactiveFormComponent<{ considerations: IAttachmentControlValue[] }> implements OnInit, OnDestroy {
@Input()
public appCollapsed = false;
@Input()
public appFinished: boolean;
@Input()
public appFormGroup = createFormGroup<{ considerations: IAttachmentControlValue[] }>({
considerations: new FormArray([])
});
public isOfficialInCharge$ = this.store.pipe(select(isOfficialInChargeSelector));
public statementId$ = this.store.pipe(select(queryParamsIdSelector));
public considerations$ = this.store.pipe(select(getConsiderationAttachments));
public fileCache$ = this.store.pipe(select(getStatementAttachmentCacheSelector));
public isStatementLoading$ = this.store.pipe(select(statementLoadingSelector));
public constructor(public readonly store: Store) {
super();
}
public ngOnInit() {
this.fileCache$.pipe(delay(0), takeUntil(this.destroy$))
.subscribe((values) => this.setValueForArray(values, "considerations"));
}
public async submit() {
await this.clearErrors();
const statementId = await this.statementId$.pipe(take(1)).toPromise();
const value = this.getValue("considerations");
this.store.dispatch(submitConsiderationFilesAction({
statementId,
value
}));
}
public async downloadAttachment(attachmentId: number) {
const statementId = await this.statementId$.pipe(take(1)).toPromise();
this.store.dispatch(startAttachmentDownloadAction({statementId, attachmentId}));
}
private async clearErrors() {
const statementId = await this.statementId$.pipe(take(1)).toPromise();
const loading = await this.isStatementLoading$.pipe(take(1)).toPromise();
if (statementId != null && !loading) {
this.store.dispatch(setErrorAction({statementId, error: 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
********************************************************************************/
export * from "./statement-details-contributions.component";
<!-------------------------------------------------------------------------------
* 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
-------------------------------------------------------------------------------->
<app-collapsible
[(appCollapsed)]="appCollapsed"
[appTitle]="('statementEditorForm.container.contributionStatus' | translate)
+ ' (' + (contributions$ | async)?.selected?.length + '/' + (requiredContributionOptions$ | async).length + ')'"
class="statement-details">
<app-select-group
*ngIf="(requiredContributionOptions$ | async)?.length > 0"
[appGroups]="requiredContributionGroups$ | async"
[appHideControls]="true"
[appOptions]="requiredContributionOptions$ | async"
[appValue]="contributions$ | async"
style="padding: 1em;">
</app-select-group>
<div *ngIf="!((requiredContributionOptions$ | async)?.length > 0)"
class="placeholder">
{{"details.contributions.placeholder" | translate}}
</div>
</app-collapsible>
/********************************************************************************
* 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
********************************************************************************/
:host {
display: block;
width: 100%;
&:empty {
display: none;
}
}
.placeholder {
padding: 1em;
font-style: italic;
}
/********************************************************************************
* 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 {provideMockStore} from "@ngrx/store/testing";
import {I18nModule} from "../../../../core/i18n";
import {StatementDetailsModule} from "../../statement-details.module";
import {StatementDetailsContributionsComponent} from "./statement-details-contributions.component";
describe("StatementDetailsContributionsComponent", () => {
let component: StatementDetailsContributionsComponent;
let fixture: ComponentFixture<StatementDetailsContributionsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StatementDetailsModule,
I18nModule
],
providers: [provideMockStore()]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatementDetailsContributionsComponent);
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 {Component, Input} from "@angular/core";
import {select, Store} from "@ngrx/store";
import {
getContributionsSelector,
requiredContributionsGroupsSelector,
requiredContributionsOptionsSelector
} from "../../../../store/statements/selectors";
/**
* This component shows the current state of contributions of the statement. This display is purely visual. No editing possible.
*/
@Component({
selector: "app-statement-details-contributions",
templateUrl: "./statement-details-contributions.component.html",
styleUrls: ["./statement-details-contributions.component.scss"]
})
export class StatementDetailsContributionsComponent {
@Input()
public appCollapsed = false;
public requiredContributionOptions$ = this.store.pipe(select(requiredContributionsOptionsSelector));
public requiredContributionGroups$ = this.store.pipe(select(requiredContributionsGroupsSelector));
public contributions$ = this.store.pipe(select(getContributionsSelector));
public constructor(public store: Store) {
}
}
/********************************************************************************
* 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 "./statement-details-geographic-position.component";
<!-------------------------------------------------------------------------------
* 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
-------------------------------------------------------------------------------->
<app-collapsible
[(appCollapsed)]="appCollapsed"
[appTitle]="'Geographische Position'">
<div *ngIf="(geographicPosition$ | async) != null"
class="map">
<app-leaflet-map
(appOpenGis)="openGis($event)"
[appCenter]="geographicPosition$ | async">
<ng-container [appLeafletMarker]="geographicPosition$ | async">
</ng-container>
</app-leaflet-map>
</div>
<div *ngIf="!((geographicPosition$ | async) != null)"
class="placeholder">
{{"details.geoPositions.placeholder" | translate}}
</div>
</app-collapsible>
/********************************************************************************
* 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";
:host {
display: block;
width: 100%;
&:empty {
display: none;
}
}
.placeholder {
padding: 1em;
font-style: italic;
}
.map {
width: 100%;
height: 30em;
padding: 1em;
box-sizing: border-box;
}
/********************************************************************************
* 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 {MockStore, provideMockStore} from "@ngrx/store/testing";
import {I18nModule} from "../../../../core/i18n";
import {ILeafletBounds} from "../../../../shared/leaflet/directives/leaflet";
import {openGisAction} from "../../../../store/geo/actions";
import {userNameSelector} from "../../../../store/root/selectors";
import {StatementDetailsModule} from "../../statement-details.module";
import {StatementDetailsGeographicPositionComponent} from "./statement-details-geographic-position.component";
describe("StatementDetailsGeographicPositionComponent", () => {
const user = "userName";
let component: StatementDetailsGeographicPositionComponent;
let fixture: ComponentFixture<StatementDetailsGeographicPositionComponent>;
let store: MockStore;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
StatementDetailsModule,
I18nModule
],
providers: [
provideMockStore({
selectors: [{selector: userNameSelector, value: user}]
})
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatementDetailsGeographicPositionComponent);
store = TestBed.inject(MockStore);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should open GIS", () => {
spyOn(store, "dispatch");
const bounds = {} as ILeafletBounds;
component.openGis(bounds);
expect(store.dispatch).toHaveBeenCalledWith(openGisAction({bounds, user}));
});
});
/********************************************************************************
* 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 {Component, Input} from "@angular/core";
import {select, Store} from "@ngrx/store";
import {take} from "rxjs/operators";
import {ILeafletBounds} from "../../../../shared/leaflet";
import {openGisAction, statementGeographicPositionSelector, userNameSelector} from "../../../../store";
/**
* This component displays the statements coordinates on a map using leaflet.
* No editing of the set coordinates is possible.
*/
@Component({
selector: "app-statement-details-geographic-position",
templateUrl: "./statement-details-geographic-position.component.html",
styleUrls: ["./statement-details-geographic-position.component.scss"]
})
export class StatementDetailsGeographicPositionComponent {
@Input()
public appCollapsed = false;
public geographicPosition$ = this.store.pipe(select(statementGeographicPositionSelector));
public userName$ = this.store.pipe(select(userNameSelector));
public constructor(public store: Store) {
}
public openGis(bounds: ILeafletBounds) {
this.userName$.pipe(take(1)).subscribe((user) => {
this.store.dispatch(openGisAction({bounds, user}));
});
}
}
......@@ -11,6 +11,12 @@
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
export * from "./attachments";
export * from "./contributions";
export * from "./geographic-position";
export * from "./information";
export * from "./linked-statements";
export * from "./process-information";