Skip to content
Snippets Groups Projects
Commit 2ffa4e7b authored by Sebastian Bergandy's avatar Sebastian Bergandy :keyboard:
Browse files

Merge branch 'OZG-6988-admin-new-statistic-fields' into 'main'

OZG-6988 implement button

See merge request !27
parents 029b66fe 612253f4
No related branches found
No related tags found
1 merge request!27OZG-6988 implement button
Showing
with 544 additions and 9 deletions
......@@ -28,6 +28,7 @@ import { Route } from '@angular/router';
import { OrganisationsEinheitFormPageComponent } from '../pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component';
import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component';
import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component';
import { StatistikFieldsFormPageComponent } from '../pages/statistik/statistik-fields-form-page/statistik-fields-form-page.component';
import { StatistikPageComponent } from '../pages/statistik/statistik-page/statistik-page.component';
import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component';
import { UserAddPageComponent } from '../pages/users-roles/user-add-page/user-add-page.component';
......@@ -82,4 +83,11 @@ export const appRoutes: Route[] = [
canActivate: [configurationGuard],
data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS },
},
{
path: ROUTES.STATISTIK_NEU,
component: StatistikFieldsFormPageComponent,
title: 'Admin | Statistik weitere Felder auswerten',
canActivate: [configurationGuard],
data: <GuardData>{ linkRelName: ConfigurationLinkRel.AGGREGATION_MAPPINGS },
},
];
<admin-statistik-fields-form data-test-id="evaluate-fields-form"></admin-statistik-fields-form>
\ No newline at end of file
import { existsAsHtmlElement } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockComponent } from 'ng-mocks';
import { AdminStatistikFieldsFormComponent } from '../../../../../../libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component';
import { getDataTestIdOf } from '../../../../../../libs/tech-shared/test/data-test';
import { StatistikFieldsFormPageComponent } from './statistik-fields-form-page.component';
describe('StatistikFieldsFormPageComponent', () => {
let component: StatistikFieldsFormPageComponent;
let fixture: ComponentFixture<StatistikFieldsFormPageComponent>;
const evaluateFieldsForm: string = getDataTestIdOf('evaluate-fields-form');
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [StatistikFieldsFormPageComponent, MockComponent(AdminStatistikFieldsFormComponent)],
}).compileComponents();
fixture = TestBed.createComponent(StatistikFieldsFormPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('template', () => {
describe('weiter felder auswerten form', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, evaluateFieldsForm);
});
});
});
});
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { AdminStatistikFieldsFormComponent } from '../../../../../../libs/admin/statistik/src/lib/statistik-fields-form/admin-statistik-fields-form.component';
@Component({
selector: 'statistik-fields-form-page',
standalone: true,
imports: [CommonModule, AdminStatistikFieldsFormComponent],
templateUrl: './statistik-fields-form-page.component.html',
})
export class StatistikFieldsFormPageComponent {}
......@@ -25,7 +25,7 @@ import { StatistikContainerComponent } from '@admin-client/statistik';
import { Component } from '@angular/core';
@Component({
selector: 'app-statistik-page',
selector: 'statistik-page',
standalone: true,
imports: [StatistikContainerComponent],
templateUrl: './statistik-page.component.html',
......
......@@ -31,8 +31,8 @@ import { Postfach, PostfachResource, PostfachSettingsItem } from './postfach.mod
@Injectable()
export class PostfachService {
private postfachResourceService = inject(PostfachResourceService)
private snackbarService = inject(SnackBarService)
private postfachResourceService = inject(PostfachResourceService);
private snackbarService = inject(SnackBarService);
public get(): Observable<StateResource<PostfachResource>> {
return this.postfachResourceService.get();
......
......@@ -28,4 +28,5 @@ export const ROUTES = {
ORGANISATIONSEINHEITEN: 'organisationseinheiten',
UNAVAILABLE: 'unavailable',
STATISTIK: 'statistik',
STATISTIK_NEU: 'statistik/neu',
};
......@@ -24,3 +24,10 @@
-->
<h1 class="heading-1" data-test-id="statistik-header-text">Statistik</h1>
<div class="mt-4">
<ods-button
text="Weitere Felder auswerten"
(clickEmitter)="navigateToStatistikFieldsForm()"
dataTestId="weitere-felder-auswerten-button"
></ods-button>
</div>
......@@ -21,16 +21,34 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ROUTES } from '@admin-client/shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { existsAsHtmlElement, mock, Mock, triggerEvent } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { getDataTestIdAttributeOf } from '../../../../../tech-shared/test/data-test';
import { StatistikContainerComponent } from './statistik-container.component';
describe('StatistikContainerComponent', () => {
let component: StatistikContainerComponent;
let fixture: ComponentFixture<StatistikContainerComponent>;
const evaluateAdditionalFieldsTestId: string = getDataTestIdAttributeOf('weitere-felder-auswerten-button');
let navigationService: Mock<NavigationService>;
beforeEach(() => {
navigationService = mock(NavigationService);
});
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [StatistikContainerComponent],
providers: [
{
provide: NavigationService,
useValue: navigationService,
},
],
});
});
......@@ -41,7 +59,44 @@ describe('StatistikContainerComponent', () => {
fixture.detectChanges();
});
describe('component', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
describe('navigateToStatistikFieldsForm', () => {
it('should call navigation service', () => {
component.navigateToStatistikFieldsForm();
expect(navigationService.navigate).toHaveBeenCalledWith(ROUTES.STATISTIK_NEU);
});
});
});
describe('template', () => {
describe('weiter felder auswerten button', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, evaluateAdditionalFieldsTestId);
});
describe('output', () => {
describe('clickEmitter', () => {
it('should call handler', () => {
component.navigateToStatistikFieldsForm = jest.fn();
fixture.detectChanges();
triggerEvent({
fixture,
elementSelector: evaluateAdditionalFieldsTestId,
name: 'clickEmitter',
});
expect(component.navigateToStatistikFieldsForm).toHaveBeenCalled();
});
});
});
});
});
});
......@@ -21,13 +21,22 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ROUTES } from '@admin-client/shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { ButtonComponent } from '@ods/system';
@Component({
selector: 'admin-statistik-container',
templateUrl: './statistik-container.component.html',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, ButtonComponent],
})
export class StatistikContainerComponent {}
export class StatistikContainerComponent {
private readonly navigationService = inject(NavigationService);
public navigateToStatistikFieldsForm(): void {
this.navigationService.navigate(ROUTES.STATISTIK_NEU);
}
}
<h2 class="heading-2" data-test-id="statistik-fields-form-header-text">Felder zur Auswertung hinzufügen</h2>
<div class="flex max-w-4xl flex-col gap-4">
<ods-text-input
[fieldControl]="formEngineFormControl"
label="Formengine"
placeholder="Tragen Sie hier die Formengine des Formulars ein"
data-test-id="form-engine-input"
></ods-text-input>
<ods-text-input
[fieldControl]="formIdFormControl"
label="FormID"
placeholder="Tragen Sie hier die FormID des Formulars ein"
data-test-id="form-id-input"
></ods-text-input>
@for (dataFieldControl of dataFieldsFormControls; track $index) {
<ods-text-input
[fieldControl]="dataFieldControl"
label="Pfad des Datenfeldes"
placeholder="Tragen Sie hier den gesamten Pfad des Datenfeldes ein, das Sie auswerten möchten."
[attr.data-test-id]="'data-statistik-field-' + $index"
></ods-text-input>
}
<ods-button text="Datenfeld hinzufügen" (clickEmitter)="addDataField()" data-test-id="add-data-field-button">
<ods-plus-icon icon class="fill-whitetext" />
</ods-button>
<div class="mt-4 flex gap-4">
<ods-button text="Speichern" data-test-id="save-statistik-fields-button"></ods-button>
<ods-button text="Abbrechen" variant="outline" (clickEmitter)="onCancel()" data-test-id="cancel-statistik-fields-button">
<ods-close-icon icon class="fill-primary" />
</ods-button>
</div>
</div>
import { ROUTES } from '@admin-client/shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { existsAsHtmlElement, getElementComponentFromFixtureByCss, mock, Mock, triggerEvent } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder } from '@angular/forms';
import { TextInputComponent } from '@ods/system';
import { getDataTestIdOf } from '../../../../../tech-shared/test/data-test';
import { AdminStatistikFieldsFormComponent } from './admin-statistik-fields-form.component';
import { StatistikFieldsFormService } from './statistik-fields.formservice';
describe('AdminStatistikFieldsFormComponent', () => {
let component: AdminStatistikFieldsFormComponent;
let fixture: ComponentFixture<AdminStatistikFieldsFormComponent>;
const formEngineInputTestId: string = getDataTestIdOf('form-engine-input');
const formIdInputTestId: string = getDataTestIdOf('form-id-input');
const addDataFieldButtonTestId: string = getDataTestIdOf('add-data-field-button');
const saveButtonTestId: string = getDataTestIdOf('save-statistik-fields-button');
const cancelButtonTestId: string = getDataTestIdOf('cancel-statistik-fields-button');
const dataField1TestId: string = getDataTestIdOf('data-statistik-field-0');
let formService: StatistikFieldsFormService;
let navigationService: Mock<NavigationService>;
beforeEach(() => {
formService = new StatistikFieldsFormService(new FormBuilder());
navigationService = mock(NavigationService);
});
beforeEach(async () => {
TestBed.overrideComponent(AdminStatistikFieldsFormComponent, {
set: {
providers: [
{
provide: StatistikFieldsFormService,
useValue: formService,
},
{
provide: NavigationService,
useValue: navigationService,
},
],
},
});
await TestBed.configureTestingModule({
imports: [AdminStatistikFieldsFormComponent],
providers: [{ provide: StatistikFieldsFormService, useValue: formService }],
}).compileComponents();
fixture = TestBed.createComponent(AdminStatistikFieldsFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
describe('component', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set form controls', () => {
expect(component.formEngineFormControl).toBeDefined();
expect(component.formIdFormControl).toBeDefined();
expect(component.dataFieldsFormControls).toBeDefined();
});
describe('addDataField', () => {
it('should call form service', () => {
formService.addDataField = jest.fn();
component.addDataField();
expect(formService.addDataField).toHaveBeenCalled();
});
});
describe('onCancel', () => {
it('should call navigation service', () => {
component.onCancel();
expect(navigationService.navigate).toHaveBeenCalledWith(ROUTES.STATISTIK);
});
});
});
describe('template', () => {
describe('form engine input', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, formEngineInputTestId);
});
it('should have been called with inputs', () => {
fixture.detectChanges();
const formEngineInput: TextInputComponent = getElementComponentFromFixtureByCss<TextInputComponent>(
fixture,
formEngineInputTestId,
);
expect(formEngineInput.fieldControl).toEqual(component.formEngineFormControl);
});
});
describe('form id input', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, formIdInputTestId);
});
it('should have been called with inputs', () => {
fixture.detectChanges();
const formIdInput: TextInputComponent = getElementComponentFromFixtureByCss<TextInputComponent>(
fixture,
formIdInputTestId,
);
expect(formIdInput.fieldControl).toEqual(component.formIdFormControl);
});
});
describe('data field input', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, dataField1TestId);
});
it('should have been called with inputs', () => {
fixture.detectChanges();
const dataFieldInput: TextInputComponent = getElementComponentFromFixtureByCss<TextInputComponent>(
fixture,
dataField1TestId,
);
expect(dataFieldInput.fieldControl).toEqual(component.dataFieldsFormControls[0]);
});
});
describe('add data field button', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, addDataFieldButtonTestId);
});
describe('output', () => {
describe('clickEmitter', () => {
it('should call handler', () => {
fixture.detectChanges();
component.addDataField = jest.fn();
triggerEvent({
fixture,
elementSelector: addDataFieldButtonTestId,
name: 'clickEmitter',
});
expect(component.addDataField).toHaveBeenCalled();
});
});
});
});
describe('save button', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, saveButtonTestId);
});
});
describe('cancel button', () => {
it('should exists', () => {
fixture.detectChanges();
existsAsHtmlElement(fixture, cancelButtonTestId);
});
describe('output', () => {
describe('clickEmitter', () => {
it('should call handler', () => {
component.onCancel = jest.fn();
fixture.detectChanges();
triggerEvent({
fixture,
elementSelector: cancelButtonTestId,
name: 'clickEmitter',
});
expect(component.onCancel).toHaveBeenCalled();
});
});
});
});
});
});
import { ROUTES } from '@admin-client/shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import { FormArray, FormControl, ReactiveFormsModule } from '@angular/forms';
import { ButtonComponent, CloseIconComponent, PlusIconComponent, TextInputComponent } from '@ods/system';
import { StatistikFieldsFormService } from './statistik-fields.formservice';
@Component({
selector: 'admin-statistik-fields-form',
standalone: true,
imports: [CommonModule, TextInputComponent, ButtonComponent, CloseIconComponent, ReactiveFormsModule, PlusIconComponent],
providers: [StatistikFieldsFormService],
templateUrl: './admin-statistik-fields-form.component.html',
})
export class AdminStatistikFieldsFormComponent {
private readonly formService = inject(StatistikFieldsFormService);
private readonly navigationService = inject(NavigationService);
public readonly formEngineFormControl: FormControl = this.formService.form.controls[
StatistikFieldsFormService.FIELD_FORM_ENGINE
] as FormControl;
public readonly formIdFormControl: FormControl = this.formService.form.controls[
StatistikFieldsFormService.FIELD_FORM_ID
] as FormControl;
public readonly dataFieldsFormControls: FormControl[] = (
this.formService.form.controls[StatistikFieldsFormService.FIELD_DATA_FIELDS] as FormArray
).controls as FormControl[];
public addDataField(): void {
this.formService.addDataField();
}
public onCancel(): void {
this.navigationService.navigate(ROUTES.STATISTIK);
}
}
import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared';
import { Injectable } from '@angular/core';
import { FormArray, FormControl, UntypedFormGroup } from '@angular/forms';
import { Resource } from '@ngxp/rest';
import { EMPTY, Observable } from 'rxjs';
@Injectable()
export class StatistikFieldsFormService extends AbstractFormService {
public static readonly FIELD_FORM_ENGINE: string = 'formEngine';
public static readonly FIELD_FORM_ID: string = 'formId';
public static readonly FIELD_DATA_FIELDS: string = 'dataFields';
protected initForm(): UntypedFormGroup {
return this.formBuilder.group({
[StatistikFieldsFormService.FIELD_FORM_ENGINE]: new FormControl(EMPTY_STRING),
[StatistikFieldsFormService.FIELD_FORM_ID]: new FormControl(EMPTY_STRING),
[StatistikFieldsFormService.FIELD_DATA_FIELDS]: new FormArray([new FormControl(EMPTY_STRING)]),
});
}
protected doSubmit(): Observable<StateResource<Resource>> {
return EMPTY;
}
protected getPathPrefix(): string {
return 'settingBody';
}
public addDataField(): void {
(this.form.controls[StatistikFieldsFormService.FIELD_DATA_FIELDS] as FormArray).push(new FormControl(EMPTY_STRING));
}
}
......@@ -62,6 +62,7 @@ export * from './lib/icons/office-icon/office-icon.component';
export * from './lib/icons/orga-unit-icon/orga-unit-icon.component';
export * from './lib/icons/ozg-logo-icon/ozg-logo-icon.component';
export * from './lib/icons/person-icon/person-icon.component';
export * from './lib/icons/plus-icon/plus-icon.component';
export * from './lib/icons/public-administration-icon/public-administration-icon.component';
export * from './lib/icons/save-icon/save-icon.component';
export * from './lib/icons/search-icon/search-icon.component';
......
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PlusIconComponent } from './plus-icon.component';
describe('PlusIconComponent', () => {
let component: PlusIconComponent;
let fixture: ComponentFixture<PlusIconComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PlusIconComponent],
}).compileComponents();
fixture = TestBed.createComponent(PlusIconComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { NgClass } from '@angular/common';
import { Component, Input } from '@angular/core';
import { twMerge } from 'tailwind-merge';
import { iconVariants, IconVariants } from '../iconVariants';
@Component({
selector: 'ods-plus-icon',
standalone: true,
imports: [NgClass],
template: `<svg
[ngClass]="[twMerge(iconVariants({ size }), 'fill-primary', class)]"
aria-hidden="true"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11 13H5V11H11V5H13V11H19V13H13V19H11V13Z" />
</svg>`,
})
export class PlusIconComponent {
@Input() size: IconVariants['size'] = 'medium';
@Input() class: string = undefined;
readonly iconVariants = iconVariants;
readonly twMerge = twMerge;
}
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import type { Meta, StoryObj } from '@storybook/angular';
import { PlusIconComponent } from './plus-icon.component';
const meta: Meta<PlusIconComponent> = {
title: 'Icons/Plus icon',
component: PlusIconComponent,
excludeStories: /.*Data$/,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<PlusIconComponent>;
export const Default: Story = {
args: { size: 'large' },
argTypes: {
size: {
control: 'select',
options: ['small', 'medium', 'large', 'extra-large', 'full'],
description: 'Size of icon. Property "full" means 100%',
table: {
defaultValue: { summary: 'medium' },
},
},
},
};
......@@ -16,8 +16,8 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@admin-client/configuration-shared": ["libs/admin/configuration-shared/src/index.ts"],
"@admin-client/configuration": ["libs/admin/configuration/src/index.ts"],
"@admin-client/configuration-shared": ["libs/admin/configuration-shared/src/index.ts"],
"@admin-client/organisations-einheit": ["libs/admin/organisations-einheit/src/index.ts"],
"@admin-client/organisations-einheit-shared": ["libs/admin/organisations-einheit-shared/src/index.ts"],
"@admin-client/postfach": ["libs/admin/postfach/src/index.ts"],
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment