Skip to content
Commits on Source (13)
stages:
- sonar-analysis
- quality
- build
- deploy
......@@ -16,6 +16,8 @@ build_branch:
DOCKER_TLS_CERTDIR: ''
DOCKER_HOST: tcp://docker:2375/
DOCKER_DRIVER: overlay2
only:
- merge_requests
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/docker:18.09
stage: build
except:
......@@ -102,36 +104,53 @@ deploy_rec:
environment:
name: rec
url: https://resin-rec.grandlyon.com
# code_analysis:
# image: skilldlabs/sonar-scanner:3.4.0
# services:
# - docker:18.09-dind
# stage: sonar-analysis
# only:
# - dev
# before_script:
# - export NODE_PATH=$NODE_PATH:`npm root -g`
# - npm install -g typescript
# script:
# - >
# sonar-scanner
# -Dsonar.projectName=${SONAR_PROJECT_KEY}
# -Dsonar.projectVersion=1.0
# -Dsonar.sourceEncoding=UTF-8
# -Dsonar.projectBaseDir=.
# -Dsonar.host.url=${SONAR_URL}
# -Dsonar.projectKey=${SONAR_PROJECT_KEY}
# -Dsonar.login=${SONAR_TOKEN}
# mr:
# variables:
# DOCKER_TLS_CERTDIR: ''
# DOCKER_HOST: tcp://docker:2375/
# DOCKER_DRIVER: overlay2
# image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/docker:18.09
# stage: build
# only:
# - merge_requests
# script:
# - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
# - docker build --pull -t "$CI_REGISTRY_IMAGE:dev" --build-arg conf=dev .
sonarqube:
stage: quality
only:
- dev
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4
variables:
SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache
GIT_DEPTH: '0' # T
cache:
key: '${CI_JOB_NAME}'
paths:
- .sonar/cache
script:
- >
sonar-scanner
-Dsonar.projectName=${SONAR_PROJECT_KEY}
-Dsonar.projectVersion=1.0
-Dsonar.sourceEncoding=UTF-8
-Dsonar.projectBaseDir=.
-Dsonar.host.url=${SONAR_URL}
-Dsonar.projectKey=${SONAR_PROJECT_KEY}
-Dsonar.login=${SONAR_TOKEN}
-Dsonar.cpd.exclusions=tests/**,src/**/*.spec.ts*
-Dsonar.qualitygate.wait=true
sonarqube-mr:
stage: quality
only:
- merge_requests
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4
variables:
SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache
GIT_DEPTH: '0' # T
cache:
key: '${CI_JOB_NAME}'
paths:
- .sonar/cache
script:
- >
sonar-scanner
-Dsonar.projectName=${SONAR_PROJECT_KEY}
-Dsonar.projectVersion=1.0
-Dsonar.sourceEncoding=UTF-8
-Dsonar.projectBaseDir=.
-Dsonar.host.url=${SONAR_URL}
-Dsonar.projectKey=${SONAR_PROJECT_KEY}
-Dsonar.login=${SONAR_MR_TOKEN}
-Dsonar.cpd.exclusions=tests/**,src/**/*.spec.ts*
-Dsonar.qualitygate.wait=true
/title [Scope] Description
### Résumé du problème
_Donnez une description briève du problème._
### Les étapes pour reproduire le bug
_Listez les étapes qui vous permettent de reproduire ce bug, cette étape est très importante._
### Décrivez le comportement du bug ?
### Quel serez le comportement attendu ?
### Logs et/ou screenshots
### Possible fixes
/label ~"type::bug"
## What does this MR do and why?
_Describe in detail what your merge request does and why._
| :warning: Keep an up to date checklist based on your icescrum tasks during all the draft phase to help any other developer who would take the job after you to finish it. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## Screenshots or screen recordings
_These are strongly recommended to assist reviewers and reduce the time to merge your change._
## How to set up and validate locally (or on alpha)
_List all steps to set up and validate the changes on local environment._
## MR acceptance checklist
_To be completed by the chosen reviewer._
<!---
Using checklists improves quality in software engineering and other jobs such as with surgeons and airline pilots.
More reading on checklists can be found in the "Checklist Manifesto": http://atulgawande.com/book/the-checklist-manifesto/
"It is common to misconceive how checklists function in complex lines of work. They are not comprehensive how-to guides, whether for building a skyscraper or getting a plane out of trouble. They are quick and simple tools aimed to buttress the skills of expert professionals." - Gawande, Atul. The Checklist Manifesto
--->
### Quality
- [ ] Confirmed
1. For the code that this change impacts, I believe that the automated tests validate functionality that is highly important to users. If the existing automated tests do not cover this functionality, I have added the necessary additional tests or I have added an issue to describe the automation testing gap and linked it to this MR.
1. I have made sure that the sonar quality coverage is up to standards.
1. I have considered the impact of this change on the front-end, back-end, and database portions of the system where appropriate and applied.
1. I have tested this MR in all supported browsers or determined that this testing is not needed.
1. I have confirmed that this change is backwards compatible across updates (migrate up needs a migrate down), or I have decided that this does not apply.
### Performance, reliability and availability
- [ ] Confirmed
1. I am confident that this MR does not harm performance, or I have asked a reviewer to help assess the performance impact.
1. I have considered the scalability risk based on future predicted growth.
### Documentation
- [ ] Confirmed
1. I have prepared a squash commit to feed the changelog linked to the current milestone.
1. I have added/updated documentation (also updated if the changes feature a deprecation) or I have decided that documentation changes are not needed for this MR.
### Security
- [ ] Confirmed
1. I have confirmed that if this MR does not contains any sensitive informations hidden in the changes.
### Deployment
- [ ] Confirmed
1. When featured on a self-data project release, i have made sure my app version in the manifest and package.json is incremented and any relative changes to the permissions are clearly written and transmitted to Cozy.
......@@ -2,6 +2,21 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [1.17.0](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/compare/v1.16.3...v1.17.0) (2022-05-30)
### Features
* **admin:** now display last update date next to structure names ([9d80147](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/9d80147c9f19143f24c84541b812e7fefd0ab059))
* **structures:** add MaisonFranceService to structure types ([177d226](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/177d226e5e301889c4a569e8beffbfa8b82dee67))
### Bug Fixes
* add sonar again ([05b5573](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/05b557390ae8f2a53f185fd59dcd0bb8e56aeb49))
* change resin website placeholder ([4c3f8dd](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/4c3f8dded9911470b060126a3a2694ea85df44f6))
* **orientation:** wrong result with address on 2 postcodes ([67a2106](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/commit/67a2106ec932b2ce9279dafbb6daf72059be7b14))
### [1.16.3](https://forge.grandlyon.com/web-et-numerique/pamn_plateforme-des-acteurs-de-la-mediation-numerique/pamn_client/compare/v1.16.2...v1.16.3) (2022-03-31)
......
{
"name": "pamn",
"version": "1.16.3",
"version": "1.17.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
{
"name": "pamn",
"version": "1.16.3",
"version": "1.17.0",
"scripts": {
"ng": "ng",
"start": "ng serve --configuration=fr --proxy-config proxy.conf.json",
......
......@@ -18,3 +18,36 @@
.incomplete {
color: $red;
}
.isOutdated {
color: $red;
}
.structure-updated-at {
text-align: right;
}
.error {
color: red;
font-weight: bold;
}
.singleCheckbox {
background: $white;
border-radius: 4px;
padding: 0 16px;
height: 44px;
padding-top: 22px;
outline: none;
border: 1px solid;
margin: 8px 0;
&.checked {
color: $white;
background: $green-1;
border: none;
}
input,
label {
cursor: pointer;
}
}
......@@ -6,6 +6,7 @@ import { ManageUsersComponent } from './components/manage-users/manage-users.com
import { SharedModule } from '../shared/shared.module';
import { NewsletterUsersComponent } from './components/newsletter-users/newsletter-users.component';
import { AdminStructuresListComponent } from './components/structures-list/admin-structures-list.component';
import { ManageLockdownInfoComponent } from './components/lockdown-info/manage-lockdown-info.component';
import { AdminRoutingModule } from './admin-routing.module';
import { AgGridModule } from 'ag-grid-angular';
import { DeleteUserComponent } from './components/manage-users/delete-user/delete-user.component';
......@@ -17,6 +18,7 @@ import { AdministredStructuresComponent } from './components/manage-users/admini
ClaimStructureComponent,
NewsletterUsersComponent,
AdminStructuresListComponent,
ManageLockdownInfoComponent,
ManageUsersComponent,
DeleteUserComponent,
AdministredStructuresComponent,
......
<div *ngIf="isLoading" class="loader">
<img class="loader-gif" src="/assets/gif/loader_circle.gif" alt />
</div>
<div *ngIf="!isLoading" fxLayout="column" fxLayoutAlign="center center">
<H2>Gestion de l'affichage des infos Covid</H2>
<div *ngIf="lockdownInfoDisplay">Les infos covid sont actuellement affichées dans les fiches structure.</div>
<div *ngIf="!lockdownInfoDisplay">Les infos covid ne sont actuellement pas affichées dans les fiches structure.</div>
<div *ngIf="!error" class="singleCheckbox" [ngClass]="{ checked: lockdownInfoDisplay }">
<input
type="checkbox"
name="lockdownInfo"
id="lockdownInfo"
[checked]="lockdownInfoDisplay"
(change)="toggleLockdownInfo()"
/>
<label for="lockdownInfo"> Afficher les infos covid</label>
</div>
<p *ngIf="error" class="error">Une erreur s'est produite a l'enregistrement, veuillez ré-essayer plus tard.</p>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ManageLockdownInfoComponent } from './manage-lockdown-info.component';
describe('ManageLockdownInfoComponent', () => {
let component: ManageLockdownInfoComponent;
let fixture: ComponentFixture<ManageLockdownInfoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ManageLockdownInfoComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ManageLockdownInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { ParametersService } from '../../services/parameters.service';
@Component({
selector: 'app-admin-manage-lockdown-info',
templateUrl: './manage-lockdown-info.component.html',
styleUrls: ['../../admin-pannel.scss'],
})
export class ManageLockdownInfoComponent implements OnInit {
public isLoading: boolean = true;
public lockdownInfoDisplay: boolean;
public error: boolean = false;
constructor(private parametersService: ParametersService) {}
ngOnInit(): void {
this.parametersService.getParameters().subscribe((params) => {
this.lockdownInfoDisplay = params.lockdownInfoDisplay;
this.isLoading = false;
});
}
public toggleLockdownInfo() {
this.lockdownInfoDisplay = !this.lockdownInfoDisplay;
this.parametersService.SetLockdownInfoDisplay(this.lockdownInfoDisplay).subscribe(
() => {},
() => {
this.error = true;
}
);
}
}
import { Component } from '@angular/core';
import { User } from '../../../models/user.model';
import { AdminService } from '../../services/admin.service';
import { DeleteUserComponent } from './delete-user/delete-user.component';
import { AdministredStructuresComponent } from './administred-structures/administred-structures.component';
import { DeleteUserComponent } from './delete-user/delete-user.component';
@Component({
selector: 'app-admin-manage-users',
......@@ -105,7 +105,7 @@ export class ManageUsersComponent {
public findAttachedUsers(): void {
this.adminService.getAttachedUsers().subscribe((users) => {
this.attachedUsers = users;
this.attachedUsers.map((user) => {
this.attachedUsers.forEach((user) => {
user._id = user['id'];
});
});
......@@ -120,7 +120,7 @@ export class ManageUsersComponent {
public findUnVerifiedUsers(): void {
this.adminService.getUnVerifiedUsers().subscribe((users) => {
this.unVerifiedUsers = users;
this.unVerifiedUsers.map((user) => {
this.unVerifiedUsers.forEach((user) => {
user._id = user['id'];
});
});
......
......@@ -4,6 +4,7 @@
<button (click)="changeActiveFeature(features.pendingStructures)">Revendication structure</button>
<button (click)="changeActiveFeature(features.structuresList)">Liste structures</button>
<button (click)="changeActiveFeature(features.deleteUsers)">Gestion des utilisateurs</button>
<button (click)="changeActiveFeature(features.lockdownInfo)">Gestion des infos covid</button>
<button (click)="changeActiveFeature(features.newsletterUsers)">Newsletter</button>
<a target="_blank" class="custom-link" rel="noopener noreferrer" [href]="ghostLink">Ghost</a>
</div>
......@@ -16,6 +17,9 @@
<div *ngIf="selectedFeature === features.pendingStructures">
<app-admin-claim-structure></app-admin-claim-structure>
</div>
<div *ngIf="selectedFeature === features.lockdownInfo">
<app-admin-manage-lockdown-info></app-admin-manage-lockdown-info>
</div>
<div *ngIf="selectedFeature === features.newsletterUsers">
<app-admin-newsletter-users></app-admin-newsletter-users>
</div>
......
......@@ -4,7 +4,7 @@
<div *ngIf="!isLoading" fxLayout="column" fxLayoutAlign="center center">
<table aria-describedby="demands attachment results" class="results-tab results-column">
<thead>
<th scope="col">
<th colspan="2" scope="colgroup">
Structures avec des données manquantes ({{ structuresIncomplete ? structuresIncomplete.length : 0 }})
</th>
</thead>
......@@ -15,6 +15,9 @@
{{ structure.structureName }}</a
>
</td>
<td class="structure-updated-at">
<span [ngClass]="{ isOutdated: structure.isOutdated }">{{ structure.updatedAt | date: 'mediumDate' }}</span>
</td>
</tr>
<tr *ngIf="!structuresIncomplete?.length">
<td colspan="3">Aucune structure</td>
......@@ -23,13 +26,16 @@
</table>
<table aria-describedby="demands attachment results" class="results-tab results-column">
<thead>
<th scope="col">Structures en cours de revendication ({{ structuresInClaim.length }})</th>
<th colspan="2" scope="colgroup">Structures en cours de revendication ({{ structuresInClaim.length }})</th>
</thead>
<tbody>
<tr *ngFor="let structure of structuresInClaim">
<td>
<a href="/acteurs?id={{ structure.structureId }}" target="_blank"> {{ structure.structureName }}</a>
</td>
<td class="structure-updated-at">
<span [ngClass]="{ isOutdated: structure.isOutdated }">{{ structure.updatedAt | date: 'mediumDate' }}</span>
</td>
</tr>
<tr *ngIf="!structuresInClaim?.length">
<td colspan="3">Aucune structure</td>
......@@ -38,13 +44,16 @@
</table>
<table aria-describedby="demands attachment results" class="results-tab results-column">
<thead>
<th scope="col">Structures à revendiquer ({{ structuresToClaim.length }})</th>
<th colspan="2" scope="colgroup">Structures à revendiquer ({{ structuresToClaim.length }})</th>
</thead>
<tbody>
<tr *ngFor="let structure of structuresToClaim">
<td>
<a href="/acteurs?id={{ structure.structureId }}" target="_blank"> {{ structure.structureName }}</a>
</td>
<td class="structure-updated-at">
<span [ngClass]="{ isOutdated: structure.isOutdated }">{{ structure.updatedAt | date: 'mediumDate' }}</span>
</td>
</tr>
<tr *ngIf="!structuresToClaim?.length">
<td colspan="3">Aucune structure</td>
......@@ -53,13 +62,16 @@
</table>
<table aria-describedby="demands attachment results" class="results-tab results-column">
<thead>
<th scope="col">Structures revendiquées ({{ structuresClaimed.length }})</th>
<th colspan="2" scope="colgroup">Structures revendiquées ({{ structuresClaimed.length }})</th>
</thead>
<tbody>
<tr *ngFor="let structure of structuresClaimed">
<td>
<a href="/acteurs?id={{ structure.structureId }}" target="_blank"> {{ structure.structureName }}</a>
</td>
<td class="structure-updated-at">
<span [ngClass]="{ isOutdated: structure.isOutdated }">{{ structure.updatedAt | date: 'mediumDate' }}</span>
</td>
</tr>
<tr *ngIf="!structuresClaimed?.length">
<td colspan="3">Aucune structure</td>
......
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ParametersService {
private readonly baseUrl = 'api/';
constructor(private http: HttpClient) {}
public getParameters(): Observable<any> {
return this.http.get<any>(`${this.baseUrl}parameters`);
}
public SetLockdownInfoDisplay(lockdownInfoDisplay: boolean): Observable<any> {
return this.http.post(`${this.baseUrl}parameters/`, { lockdownInfoDisplay });
}
}
......@@ -25,8 +25,15 @@
</div>
<div class="beneficiary">
<div class="informationHeader">{{ 'Bénéficiaire' | uppercase }}</div>
<!-- Name -->
<table class="beneficiaryNeeds">
<caption style="display: none">
Résumé du besoin de l'utilisateur pour impresion.
</caption>
<thead style="display: none">
<th>Résumé de besoin</th>
</thead>
<!-- Name -->
<tr>
<td class="informationHeader">Nom</td>
<td class="bold">{{ beneficiaryName }}</td>
......
import { stringify } from '@angular/compiler/src/util';
import { Component, EventEmitter, HostListener, OnInit, Output } from '@angular/core';
import { AbstractControl, Form, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
import { Component, HostListener, OnInit } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
import { Meta } from '@angular/platform-browser';
import { GeoJson } from '../../map/models/geojson.model';
import { Address } from '../../models/address.model';
import { OrientationFormFilters } from '../../models/orientation-filter.object';
import { Structure } from '../../models/structure.model';
......@@ -10,14 +8,13 @@ import { GeojsonService } from '../../services/geojson.service';
import { RouterListenerService } from '../../services/routerListener.service';
import { StructureService } from '../../services/structure.service';
import { CategoryEnum } from '../../shared/enum/category.enum';
import * as _ from 'lodash';
import { Category } from '../../structure-list/models/category.model';
import { Filter } from '../../structure-list/models/filter.model';
import { Module } from '../../structure-list/models/module.model';
import { SearchService } from '../../structure-list/services/search.service';
import { PageTypeEnum } from './pageType.enum';
import { Utils } from '../../utils/utils';
import { CustomRegExp } from '../../utils/CustomRegExp';
import { Utils } from '../../utils/utils';
import { PageTypeEnum } from './pageType.enum';
@Component({
selector: 'app-orientation-form',
......@@ -163,6 +160,7 @@ export class OrientationFormComponent implements OnInit {
numero: new FormControl(''),
street: new FormControl('', Validators.required),
commune: new FormControl('', Validators.required),
postcode: new FormControl(''),
}),
structureAccompaniment: new FormControl(orientationFormFilters.structureAccompaniment, Validators.required),
contactAccompanimentPhone: new FormControl(
......@@ -344,6 +342,7 @@ export class OrientationFormComponent implements OnInit {
this.getOrientationControl('address').get('numero').setValue(address.numero);
this.getOrientationControl('address').get('street').setValue(address.street);
this.getOrientationControl('address').get('commune').setValue(address.commune);
this.getOrientationControl('address').get('postcode').setValue(address.postcode);
this.userLocation = address.coordinates;
} else {
this.orientationForm.get('address').reset();
......@@ -441,7 +440,11 @@ export class OrientationFormComponent implements OnInit {
private setStructuresAndCoord(): void {
this.geoJsonService
.getCoord(this.orientationForm.value.address.numero, this.orientationForm.value.address.street, '69000')
.getCoord(
this.orientationForm.value.address.numero,
this.orientationForm.value.address.street,
this.orientationForm.value.address.postcode ? this.orientationForm.value.address.postcode : '69000'
)
.subscribe((res) => {
this.structureService.getStructures(this.filters.filter((elem) => elem.checked == true)).subscribe((data) => {
data.map((structure) => {
......@@ -518,6 +521,7 @@ export class OrientationFormComponent implements OnInit {
}
this.getOrientationControl('address').get('street').setValue(location.properties.street);
this.getOrientationControl('address').get('commune').setValue(location.properties.city);
this.getOrientationControl('address').get('postcode').setValue(location.properties.postcode);
this.setValidationsForm();
this.isLoading = false;
},
......
......@@ -647,7 +647,7 @@
<div fxLayout="row" fxLayoutGap="27px">
<input
type="text"
placeholder="www.resin.grandlyon.com"
placeholder="www.grandlyon.com"
(input)="setValidationsForm()"
formControlName="website"
class="form-input"
......
export class Address {
numero: string = null;
street: string = null;
commune: string = null;
coordinates? = [];
}
export class Address {
numero: string = null;
street: string = null;
commune: string = null;
postcode?: number = null;
coordinates? = [];
}
......@@ -59,6 +59,7 @@ export class AddressAutocompleteComponent implements OnInit {
address.numero = hit.properties.housenumber ? hit.properties.housenumber : null;
address.commune = hit.properties.city;
address.coordinates = hit.geometry.coordinates;
address.postcode = hit.properties.postcode;
if (hit.properties.street) {
address.street = hit.properties.street;
} else {
......