Skip to content
Commits on Source (15)
......@@ -2,3 +2,5 @@
All notable changes to [ngpr_prodige_contribution](https://gitlab.adullact.net/prodige/ngpr_prodige_contribution) project will be documented in this file.
## [5.0.2](https://gitlab.adullact.net/prodige/ngpr_prodige_contribution/compare/5.0.1...5.0.2) - 2023-03-07
5.0.1
\ No newline at end of file
5.0.2
\ No newline at end of file
......@@ -19,6 +19,12 @@ module.exports = {
"plugin:@angular-eslint/template/process-inline-templates",
],
rules: {
"@typescript-eslint/unbound-method": [
"error",
{
"ignoreStatic": true
}
],
"@angular-eslint/component-selector": [
"error",
{
......
......@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"dev": "eslint \"src/app/**/*.ts\" --fix && ng serve --host 0.0.0.0 --port 4200 --disable-host-check",
"dev": "eslint \"src/app/**/*.ts\" --fix && ng serve --host 0.0.0.0 --port 80 --disable-host-check",
"login": "npm login --registry=https://nexus.alkante.com/repository/npm/ --scope=@alkante",
"start": "ng serve",
"build": "ng build",
......
......@@ -26,7 +26,7 @@ import { DragulaModule } from 'ng2-dragula';
HttpClientModule,
VisualiseurCoreModule,
MapModule,
DragulaModule.forRoot(),
DragulaModule,
],
providers: [EnvServiceProvider],
bootstrap: [AppComponent],
......
<form class=" row g-3">
<form class=" row g-3" [formGroup]="formGroup" *ngIf="formGroup">
<div class="col-lg-4 mb-3">
<label class="form-label" i18n="@@organisationName">Nom de l'organisation</label>
<input type="name" name="contact-name" class="form-control" id="inputNameOrga" aria-describedby="NameOrga"
[(ngModel)]="contact.organization" (change)="onChange()" [ngbTypeahead]="this.orgaAutoComplete"
formControlName="organization" [ngbTypeahead]="this.orgaAutoComplete"
>
<alk-form-error [control]="formGroup" field="organization"></alk-form-error>
</div>
<div class="col-lg-4 mb-3">
<label class="form-label" i18n="@@MailAdress">Adresse mail</label>
<input type="email" class="form-control" id="mailAdress" placeholder="nom@mail.com" name="contact-mail-address"
[(ngModel)]="contact.mail" (change)="onChange()">
formControlName="mail" >
<alk-form-error [control]="formGroup" field="mail"></alk-form-error>
</div>
<div class="col-lg-3 mb-3 ">
<label class="form-label" i18n="@@Role">Rôle</label>
<select class="form-control" aria-label="Default select example" name="contact-role"
[(ngModel)]="contact.role" (change)="onChange()">
formControlName="role" >
<option selected [value]="undefined" disabled i18n="@@roleSélecion">Sélectionner un rôle</option>
<ng-container *ngFor="let role of roleCodes">
<option [value]="role.code"> {{role.label}}</option>
</ng-container>
</select>
<alk-form-error [control]="formGroup" field="role"></alk-form-error>
</div>
<div class="col-lg-1 mb-3" *ngIf="total > 1">
<label class="form-label" i18n="@deletion" >Suppression</label>
......
......@@ -6,6 +6,7 @@ import { RoleCode } from '../../models/role-code';
import { EnvService } from '../../services/env/env.service';
import { OrganizationService } from '../../services/organization.service';
import { RoleService } from '../../services/role.service';
import { AbstractControl, FormGroup } from '@angular/forms';
@Component({
selector: `alk-contact`,
......@@ -13,16 +14,17 @@ import { RoleService } from '../../services/role.service';
styleUrls: [`./contact.component.scss`],
})
export class ContactComponent extends BaseComponent implements OnInit{
@Input() contact!: Contact;
@Input() contactForm!: FormGroup|AbstractControl;
@Input() total!: number;
@Output() contactChange = new EventEmitter<Contact>();
@Output() removeContact = new EventEmitter<Contact>();
public roleCodes: RoleCode[] = [];
public orgaAutoComplete: OperatorFunction<string, readonly string[]>;
public formGroup:FormGroup = null;
constructor(
private envService: EnvService,
private organizationService: OrganizationService,
......@@ -34,6 +36,8 @@ export class ContactComponent extends BaseComponent implements OnInit{
}
ngOnInit(): void {
this.formGroup = <FormGroup> this.contactForm;
this.loadRoles();
this.organizationService.loadOrganization$().pipe(
......@@ -43,12 +47,8 @@ export class ContactComponent extends BaseComponent implements OnInit{
});
}
public onChange(): void{
this.contactChange.emit( this.contact );
}
public remove(): void{
this.removeContact.emit( this.contact );
// this.removeContact.emit( this.contact.value );
}
private loadRoles() {
......
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContactComponent } from './contact.component';
import { FormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
import { FormErrorModule } from '../form-error/form-error.module';
......@@ -17,6 +18,8 @@ import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
CommonModule,
FormsModule,
NgbTypeaheadModule,
ReactiveFormsModule,
FormErrorModule,
],
})
export class ContactModule { }
<ng-container *ngIf="this.ctrlError as ctrlError">
<ng-container *ngIf="ctrlError?.invalid && (ctrlError.touched || ctrlError.dirty)">
<div class="alert-form form-text">
<ng-container *ngFor="let error of ctrlError.errors | keyvalue">
<ng-container [ngSwitch]="error.key">
<ng-container *ngSwitchCase="'email'">
<p class="my-0 text-danger">Cette adresse mail n'est pas valide.</p>
</ng-container>
<ng-container *ngSwitchCase="'required'">
<p class="my-0 text-danger">Ce champ est requis.</p>
</ng-container>
<ng-container *ngSwitchCase="'minlength'">
<p class="my-0 text-danger">Ce champ doit contenir au moins {{ error.value['requiredLength'] }} éléments.
({{ error.value['actualLength']}} actuellement)</p>
</ng-container>
<ng-container *ngSwitchCase="'maxlength'">
<p class="my-0 text-danger">Ce champ doit contenir au plus {{ error.value['requiredLength'] }} éléments.
({{ error.value['actualLength']}} actuellement)</p>
</ng-container>
<ng-container *ngSwitchCase="'max'">
<p class="my-0 text-danger">Ce champ doit valoir au plus {{ error.value['max'] }}.
({{ error.value['actual']}} actuellement)</p>
</ng-container>
<ng-container *ngSwitchCase="'min'">
<p class="my-0 text-danger">Ce champ doit valoir au moins {{ error.value['min'] }}.
({{ error.value['actual']}} actuellement)</p>
</ng-container>
<ng-container *ngSwitchCase="'pattern'">
<ng-container *ngIf="error.value['requiredPattern'] as reqPattern">
<p class="my-0 text-danger">Valeur invalide</p>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</div>
</ng-container>
</ng-container>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormErrorComponent } from './form-error.component';
describe('FormErrorComponent', () => {
let component: FormErrorComponent;
let fixture: ComponentFixture<FormErrorComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FormErrorComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FormErrorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
@Component({
selector: `alk-form-error`,
templateUrl: `./form-error.component.html`,
styleUrls: [`./form-error.component.scss`],
})
export class FormErrorComponent implements OnInit{
@Input() control: FormGroup | FormArray | AbstractControl;
@Input() field!: string | number;
public ctrlError: AbstractControl;
ngOnInit() {
if ( !this.control ) { throw new Error( `missing *form* input` ) }
if ( this.field == undefined ) { throw new Error( `missing *controlName* input` ) }
if ( this.control instanceof FormArray ) {
this.ctrlError = this.control.at( <number> this.field );
} else {
console.log( 22, this.field );
this.ctrlError = this.control.get( <string> this.field );
}
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormErrorComponent } from './form-error.component';
@NgModule({
declarations: [
FormErrorComponent,
],
exports: [
FormErrorComponent,
],
imports: [
CommonModule,
],
})
export class FormErrorModule { }
import { z } from 'zod';
export const responsePatchDataValidator = z.object({
id: z.number(),
id: z.number(),
'@type': z.string(),
});
export type ResponsePatchData = z.infer<typeof responsePatchDataValidator>;
......@@ -19,7 +19,7 @@ export const contributionFieldValidaor = z.object({
selected: z.boolean().optional(),
});
function transformDate( val: string, ctx: RefinementCtx ){
function transformDate( val: string, ctx: RefinementCtx ): string{
const date = new Date( val );
if ( !date ){
......@@ -50,7 +50,9 @@ export const contributionValidator = z.object({
abstract: z.string().nullable().optional(),
lineage: z.string().nullable().optional(),
createdAt: z.string().optional().transform( transformDate ),
createDate: z.string().optional(),
updatedAt: z.string().optional().transform( transformDate ),
updateDate: z.string().optional(),
themeKeyword: z.record( z.string().array()).optional(),
localisationKeyword: z.record( z.string().array()).optional(),
equivalentScale: z.number().optional(),
......@@ -58,6 +60,7 @@ export const contributionValidator = z.object({
subdomains: z.number().array().optional(),
openData: z.boolean().optional(),
contact: contactValidator.array().optional(),
type: z.string().optional(),
status: z.string().transform(( val, ctx ) =>{
const strSplited = val.split( `/` );
if ( strSplited.length !== 4 ){
......@@ -85,3 +88,24 @@ export const contributionValidator = z.object({
export type ContributionExtent = z.infer<typeof contributionExtentValidator>;
export type ContributionField = z.infer<typeof contributionFieldValidaor>;
export type Contribution = z.infer<typeof contributionValidator>;
export const CONTRIBUTION_KEY_PATCH = [
`status`,
`fields`,
`table`,
`title`,
`abstract`,
`lineage`,
`localisationKeyword`,
`themeKeyword`,
`equivalentScale`,
`contact`,
`geonetworkStatus`,
`openData`,
`subdomains`,
`createdAt`,
`updatedAt`,
`extent`,
`type`,
`layerId`,
];
......@@ -37,6 +37,6 @@ export class CasService {
*/
logout( serviceUrl?: string ): void {
const serviceEnconde = encodeURI( serviceUrl ? serviceUrl : `https://catalogue.prodige.internal/geonetwork` );
window.location.href = `https://cas.prodige.internal/logout?service=${serviceEnconde}`;
window.location.href = `https://cas.prodige.internal/logout?service=${serviceEnconde}`;// TODO: disconet all
}
}
......@@ -17,6 +17,8 @@ import VectorLayer from 'ol/layer/Vector';
export class ContextService extends GetDataService<FeatureCollection>{
private featureExtent: Feature<Polygon>;
private layer: VectorLayer<VectorSource>;
constructor(
protected override httpClient: HttpClient,
protected envService: EnvService,
......@@ -25,11 +27,15 @@ export class ContextService extends GetDataService<FeatureCollection>{
}
getContext$( mapFile: string ): Observable<FeatureCollection>{
// const url = `${this.envService.serverUrl}/carto/context?account=1&contextPath=/${mapFile}`;
// TODO ATTENTION URL FAKE SEULEMENT POUR LES DEV, EN ATTEDANT LE VISUALISEUR
// on supprime l'ancienne feature à la création d'une carte
this.featureExtent = null;
const url = `${this.envService.serverUrl}/carto/context?account=1&contextPath=/${mapFile}`;
// eslint-disable-next-line max-len
const url = `https://carto-test.atlasante.fr/carto/context?account=1&contextPath=/layers/3f719270-f589-460b-bf2c-6f75e856ae23.map&extent=-614973,5053097,1191092,6637194&callback=ng_jsonp_callback_4`;
return this.getData$( url, null, null, true );
// const url = `https://carto-test.atlasante.fr/carto/context?account=1&contextPath=/layers/3f719270-f589-460b-bf2c-6f75e856ae23.map&extent=-614973,5053097,1191092,6637194&callback=ng_jsonp_callback_4`;
return this.getData$( url, null, { withCredentials: true }, false );
}
drawContextEtent( map: Map, extent: ContributionExtent ): void {
......@@ -54,11 +60,11 @@ export class ContextService extends GetDataService<FeatureCollection>{
features: [this.featureExtent],
});
const layer = new VectorLayer({
this.layer = new VectorLayer({
source: vectorSource,
});
layer.setMap( map );
this.layer.setMap( map );
} else {
this.featureExtent.setGeometry( new Polygon([coordinates]));
}
......@@ -66,4 +72,10 @@ export class ContextService extends GetDataService<FeatureCollection>{
map.getView().fit([ extent.xmin, extent.ymin, extent.xmax, extent.ymax ]);
}
public updateShowExtent( map: Map ): void{
if ( this.layer ){
this.layer.setMap( map );
}
}
}
......@@ -4,6 +4,7 @@ import { map, Observable, ReplaySubject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { EnvService } from './env/env.service';
import * as moment from 'moment';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
@Injectable({
providedIn: `root`,
......@@ -13,9 +14,16 @@ export class ContributionService {
private contribution!: Contribution;
private contribution$ = new ReplaySubject<Contribution>( 1 ); // bufferSize à 1 pour avoir seulement la dernière valeur
private dateFormat = `YYYY-MM-DD`;
private contributionForm: FormGroup;
constructor(
private httpClient: HttpClient,
private envService: EnvService,
private fb: FormBuilder,
) { /* empty */ }
public loadContributionById( contributionId: number ): Observable<Contribution>{
......@@ -33,17 +41,23 @@ export class ContributionService {
return field;
});
this.contribution.createdAt = this.transformDate( this.contribution.createdAt );
this.contribution.updatedAt = this.transformDate( this.contribution.updatedAt );
this.contribution.createDate = this.transformDate( this.contribution.createdAt );
this.contribution.updateDate = this.transformDate( this.contribution.updatedAt );
this.contribution.openData = !!this.contribution.openData;
this.contribution.subdomains = this.contribution.subdomains ? this.contribution.subdomains : [];
if ( !this.contribution.contact ){
this.contribution.contact = [];
if ( !this.contribution.themeKeyword || this.contribution.themeKeyword instanceof Array ){
this.contribution.themeKeyword = null;
}
if ( !this.contribution.themeKeyword || this.contribution.localisationKeyword instanceof Array ) {
this.contribution.localisationKeyword = null;
}
this.initFormControl();
return contribution;
}),
);
......@@ -58,6 +72,15 @@ export class ContributionService {
this.contribution$.next( contribution );
}
public getDateFormat(): string{
return this.dateFormat;
}
public getContributionForm(){
return this.contributionForm;
}
private transformDate( val: string ): string{
const date = new Date( val );
......@@ -65,4 +88,78 @@ export class ContributionService {
return moment( date ).format( `YYYY-MM-DD` );
}
private initFormControl(){
const extentControl = [
Validators.required,
Validators.pattern( /\d+/ ),
];
this.contributionForm = new FormGroup({
DataOverview: new FormGroup({
'extent': new FormGroup({
'xmin': new FormControl( this.contribution.extent.xmin, extentControl ),
'ymin': new FormControl( this.contribution.extent.ymin, extentControl ),
'xmax': new FormControl( this.contribution.extent.xmax, extentControl ),
'ymax': new FormControl( this.contribution.extent.ymax, extentControl ),
}),
'sourceEncoding': new FormControl( this.contribution.sourceEncoding, Validators.required ),
'fields': new FormArray(
this.contribution.fields.map(( field ) => (
new FormGroup({
'name': new FormControl( field.name ),
'alias': new FormControl( field.alias, [Validators.required]),
'description': new FormControl( field.description ),
'type': new FormControl( field.type ),
'selected': new FormControl( field.selected ),
})
)),
),
}),
GeneralInformation: new FormGroup({
'table': new FormControl( this.contribution.table, [
Validators.required,
Validators.pattern( /^[a-zA-Z0-9_]+$/ ),
]),
'title': new FormControl( this.contribution.title, [Validators.required]),
'abstract': new FormControl( this.contribution.abstract, [Validators.required]),
'lineage': new FormControl( this.contribution.lineage ),
'themeKeyword': new FormControl( this.contribution.themeKeyword ),
'localisationKeyword': new FormControl( this.contribution.localisationKeyword ),
'updatedAt': new FormControl( this.contribution.updatedAt, [Validators.required]),
'createdAt': new FormControl( this.contribution.createdAt, [Validators.required]),
'updateDate': new FormControl( this.contribution.updateDate, [Validators.required]),
'createDate': new FormControl( this.contribution.createDate, [Validators.required]),
'equivalentScale': new FormControl( this.contribution.equivalentScale, [Validators.required]),
'geonetworkStatus': new FormControl( this.contribution.geonetworkStatus ),
'contact': new FormArray( this.getContactForm()),
}),
DiffusionModality: new FormGroup({
'openData': new FormControl( this.contribution.openData ),
'subdomains': new FormControl( this.contribution.subdomains ),
}),
});
}
public buildContact(): FormGroup{
return this.fb.group({
'organization': [ ``, [Validators.required]],
'mail': [ ``, [ Validators.email, Validators.required ]],
'role': [ null, [Validators.required]],
});
}
private getContactForm(): FormGroup[]{
if ( !( this.contribution.contact instanceof Array ) || this.contribution.contact.length === 0 ){
return [this.buildContact()];
}
return this.contribution.contact.map(( contact ) => (
this.fb.group({
'organization': [ contact.organization, Validators.required ],
'mail': [ contact.mail, [ Validators.email, Validators.required ]],
'role': [ contact.role, Validators.required ],
})
));
}
}
......@@ -9,7 +9,7 @@ import { EnvService } from './env/env.service';
@Injectable({
providedIn: `root`,
})
export class OrganizationService extends GetDataService<ProdigeGroupApi>{
export class OrganizationService extends GetDataService<ProdigeGroupApi> {
private organization: string[] = [];
......@@ -21,10 +21,11 @@ export class OrganizationService extends GetDataService<ProdigeGroupApi>{
}
loadOrganization$(): Observable<boolean> {
// this.searchTest();
const url = `${this.envService.catalogueUrl}/geonetwork/srv/api/groups?`;
return super.getData$( url, prodigeGroupeApiValidator ).pipe(
map(( result ) =>{
map(( result ) => {
this.organization = result.map(( prodigeGroupe ) => prodigeGroupe.name );
return true;
}),
......@@ -39,4 +40,50 @@ export class OrganizationService extends GetDataService<ProdigeGroupApi>{
term.length < 1 ? [] : this.organization.filter(( v ) => v.toLowerCase().indexOf( term.toLowerCase()) > -1 ).slice( 0, 10 ),
),
);
searchTest() {
/* const body = {
"aggregations": { "groupPublished": { "terms": { "field": ``, "size": 10 }}},
"from": 0,
"size": 20,
"query": { "function_score": { "query": { "bool": { "must": [{ "terms": { "isTemplate": [`s`]}}, { "terms": { "root": [`gmd:CI_ResponsibleParty`]}}]}}}},
"_source": { "includes": [ `id`, `uuid`, `creat*`, `group*`, `resource*`, `owner*` ]},
};*/
/* const body = {
"from": 0,
"size": 20,
"sort": [{ "resourceTitleObject.default.keyword": `asc` }, `_score` ],
"query": { "function_score": { "query": { "bool": { "must": [{ "terms": { "isTemplate": [`s`]}}, { "terms": { "root": [`gmd:CI_ResponsibleParty`]}}]}}}},
"aggregations": { "groupPublished": { "terms": { "field": `groupPublished`, "size": 10 }}},
"_source": { "includes": [ `id`, `uuid`, `creat*`, `group*`, `resource*`, `owner*` ]},
"track_total_hits": true,
};*/
const body = {
"from": 0,
"size": 20,
"sort": [{ "resourceTitleObject.default.keyword": `asc` }, `_score` ],
"query": { "function_score": { "query": { "bool": { "must": [{ "terms": { "isTemplate": [`s`]}}]}}}},
"aggregations": {
"valid": { "terms": { "field": `valid`, "size": 10 }},
"groupOwner": { "terms": { "field": `groupOwner`, "size": 10 }},
"recordOwner": { "terms": { "field": `recordOwner`, "size": 10 }},
"groupPublished": { "terms": { "field": `groupPublished`, "size": 10 }},
"isHarvested": { "terms": { "field": `isHarvested`, "size": 2 }},
},
"_source": { "includes": [ `id`, `uuid`, `creat*`, `group*`, `resource*`, `owner*`, `recordOwner`, `status*`, `isTemplate`, `valid`, `isHarvested`, `changeDate`, `documentStandard` ]},
"track_total_hits": true,
};
this.httpClient.post( `${this.envService.catalogueUrl}/geonetwork/srv/api/search/records/_search`, body, { withCredentials: true })
.subscribe(( result ) => {
console.log( result );
});
}
}
......@@ -113,6 +113,7 @@ export class ProdigeThemeService {
* @private
*/
public thesarusToItem( reccordKeyWords: ReccordKeyWord ): SelectedKeyWord[]{
console.log( `thesarusToItem `, reccordKeyWords );
if ( !reccordKeyWords ){
return [];
}
......