Giter Site home page Giter Site logo

asit-asso / extract Goto Github PK

View Code? Open in Web Editor NEW
9.0 2.0 3.0 47.76 MB

EXTRACT makes it easy to extract and deliver of your geodata

License: GNU General Public License v3.0

Java 78.55% HTML 14.69% CSS 0.96% JavaScript 5.74% Shell 0.04% Dockerfile 0.02%
geodata extraction automation shapefile fme fme-server fmw extract

extract's Introduction

Extract

Extract est une application qui facilite l’extraction et la livraison de vos géodonnées

L'application Extract importe les commandes de données déposées sur une plateforme ou magasin de données (comme les portails ASIT viageo.ch et plans-reseaux.ch), puis exécute une série de tâches préconfigurées afin d'extraire la donnée demandée , puis renvoie le résultat vers le client : avec ou sans intervention humaine, c'est vous qui le définissez !

En automatisant le processus d'extraction et de livraison de vos géodonnées, vous :

  • diminuez les temps de traitement des commandes,
  • augmentez la qualité des données livrées,
  • augmentez la satisfaction client.

Extract Robot

Extract est une application open source, qui s'installe chez vous

Extract Robot

Extract est modulable et extensible, à installer dans l’environnement informatique de chaque fournisseur de données, et accessible via un navigateur.

Groupe utilisateurs

L'ASIT, des administrations et gestionnaires de réseaux forment un groupe utilisateur qui pilote et finance le projet

Membres du groupe utilisateur

En production

Actuellement, 37 fournisseurs diffusent tout ou partie de leurs géodonnées avec Extract grâce aux 11 instances installées chez :

  • Ville de Nyon & TRN SA
  • Ville de Pully & Belmont-sur-Lausanne
  • Romande Energie SA & SIE SA
  • Ville de Lausanne
  • Bureau Jaquier Pointet SA qui gère la diffusion de 17 communes en délégation
  • Cartoriviera qui gère la diffusion de 9 communes et associations intercommunales (SIGE)
  • Ville de Morges
  • Viteos SA
  • SITN
  • Holdigaz Prestations SA
  • ASIT - Association pour le système d'information du territoire

Pour installer

Suivez les guides d'installation et d'exploitation ici : https://github.com/asit-asso/extract/wiki

Prérequis:

  • Windows or Linux, 64bit
  • Java 17 (Oracle ou OpenJDK)
  • Tomcat 9 (Extract n’est actuellement pas compatible avec Tomcat 10 en raison de l’utilisation de Spring Boot 2)
  • PostgreSQL >= 12

Documentation et liens:

Aide et documentation : https://github.com/asit-asso/extract/wiki

Présentations sur le projet aux Rencontres ASIT : https://asit-asso.ch/toutes-les-rencontres#2018

L'ASIT, Association pour le Système d'Information du Territoire, à l'origine du projet : https://asit-asso.ch

Forked from easySDI (www.easysdi.org) : https://svn.easysdi.org/svn/easysdi/branches/4.5.x/java

Screenshots:

easysdi Extract Home screenshot

extract's People

Contributors

arxit-ygr avatar dependabot[bot] avatar guillaumestark avatar yblatti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

extract's Issues

Ajout des résolutions dans la vue OpenLayers [redmine:22383]

Original author : Rémi Bovard

Afin d'utiliser les tuiles WMTS de façon optimales, il est nécessaire de définir en plus de la projection, les résolutions de la vue.

En me basant sur la doc pour le WMTS 2056 [1], les valeurs sont les suivantes :

<code class="javascript">
resolutions: [650, 500, 250, 100, 50, 20, 10, 5, 2.5, 2, 1, 0.5, 0.25, 0.1]

A ajouter dans le fichier @map.custom.js.dist@

[1] https://api3.geo.admin.ch/services/sdiservices.html#gettile

Budget : 545

Ajout d'une option "horaire/jour d'activité" [redmine:22299]

Original author : Xavier Mérour

Rendre possible (en option) le paramétrage des jours de la semaine et/ou des plages horaires de fonctionnement d'EXTRACT, ou son arrêt.

Cas d'utilisations possibles :

  • empêcher l'import de demande durant des plages de maintenance (nuit ou week-end par exemple)
  • empêcher le lancement de tâche d'extraction en dehors des heures de bureau (concurrence de licence FME par ex.)
  • ...

Configuration

Ajouter un bloc de paramètres "Heures de fonctionnement"

  • Proposer trois modes :
    ** "Tout le temps (24/7)" : équivalent v1.1: EXTRACT fonctionne dès que l'application est déployée, valeur par défaut pour nouvelle installation et mise à jour depuis 1.0-1.1
    ** "Seulement durant les heures ci-dessous" : ajouter 1 à n des plage 'du - au - de - à '
    *** L'administrateur peut configurer 1 à plusieurs plages jours-heures
    *** Les plages peuvent se chevaucher
    *** Un bouton à la fin de ligne permet de supprimer une plage
    *** Les plages (heure et jour) sont inclusives
    ** "Arrêt complet" : aucune activité jusqu'à ce ce paramètre soit modifié

Comportement

  • En fonctionnement : soit en mode "Tout le temps (24/7)" ou en mode "Seulement durant les heures ci-dessous" durant une plage définie
    ** EXTRACT permet les tâches manuelles
    ** EXTRACT effectue les tâches automatiques (avancement et exécution des tâches, import, export)
  • A l'arrêt : soit en mode "Arrêt complet" ou en mode "Seulement durant les heures ci-dessous" hors des plages définies
    ** EXTRACT permet les tâches manuelles
    ** EXTRACT n'effectue aucune tâche automatique (avancement et exécution des tâches, import, export)
    ** La page d’accueil affiche la raison de l'arrêt
    *** Si arrêt complet : "L'administrateur a temporairement stoppé les traitements automatiques."
    *** Si arrêt hors plage horaire : "Les traitements automatiques sont actuellement stoppés car hors périodes de fonctionnement définies par l'administrateur."

Maquettes

Configuration

extract_times

Accueil hors plages horaires

extract_business_hours

Accueil si arrêt complet

extract_off

Critères d’acceptation

|.ID|.Critère|
|22299-1|La configuration propose un nouveau bloc "Heures de fonctionnement"|
|22299-2|Le bloc "Heures de fonctionnement" propose le choix de trois modes : "Tout le temps (24/7)" , "Seulement durant les heures ci-dessous" et "Arrêt complet"|
|22299-3|Les plages de fonctionnement peuvent se chevaucher et se croiser : exemples valables
+du LUN au VEN de 08:00 à 16:00+ ET +du VEN au VEN de 08:00 à 18:00+
+du LUN au JEU de 08:00 à 16:00+ ET +du VEN au VEN de 08:00 à 12:00+
+du LUN au VEN de 08:00 à 11:30+ ET +du LUN au VEN de 13:30 à 18:00+ ET +du SAM au SAM de 09:30 à 12:00+
+du LUN au MAR de 08:00 à 13:00+ ET +du MAR au VEN de 12:00 à 16:00+
|
|22299-4|La page d’accueil affiche la raison de l'arrêt|
|22299-5|Lorsque EXTRACT est arrêté aucun import n'est effectué|
|22299-6|Lorsque EXTRACT est arrêté aucun export n'est effectué|
|22299-7|Lorsque EXTRACT est arrêté aucun traitement automatique n'est lancé|
|22299-8|Dans l'écran de paramètres, les plages d'heures sont affichées et sauvées, peu importe le "mode" choisi, pour ne pas perdre la configuration|

Budget : 6987

Retour de l'erreur FME dans EXTRACT [redmine:22424]

Original author : Rémi Bovard

Dans la version 1.1 d'EXTRACT, lors d'une erreur FME le retour n'est pas explicite :


Erreur  / (-1)

Dans la version 1.0, cette même erreur était retournée ainsi :


Erreur  / FME license system failure: The maximum number of concurrent FME instances specified in the license has been reached (-508) Program Terminating Translation FAILED. (-1)

Ce qui était bien plus explicite.

Serait-il possible de revenir au comportement précédent ?

Budget : 430

Prise en charge des parenthèses dans les règles [redmine:19672]

Original author : Yves Blatti

Afin de pouvoir créer des règles complexes avec plusieurs opérateurs booléen il faut ajouter le support des parenthèses dans les règles.

Exemple de règles possible :

productguid == "81c452ca-1bef-4544-2dc5-7e2844134c38" 
and perimeter intersects POLYGON((6.55950172542713084 46.48890379920126747, 6.55774390464311452 46.59684404773618382, 6.72901850071676133 46.59804302426485378, 6.73043495839145844 46.49010040627619844, 6.55950172542713084 46.48890379920126747)) 
and (parameters.format != "LAS" OR surface > 1000000 and parameters.format == "LAS")

Cette règle doit actuellement écrite ainsi :

productguid == "81c452ca-1bef-4544-2dc5-7e2844134c38" 
and perimeter intersects POLYGON((6.55950172542713084 46.48890379920126747, 6.55774390464311452 46.59684404773618382, 6.72901850071676133 46.59804302426485378, 6.73043495839145844 46.49010040627619844, 6.55950172542713084 46.48890379920126747))
and parameters.format != "LAS" 

OR

productguid == "81c452ca-1bef-4544-2dc5-7e2844134c38"
and perimeter intersects POLYGON((6.55950172542713084 46.48890379920126747, 6.55774390464311452 46.59684404773618382, 6.72901850071676133 46.59804302426485378, 6.73043495839145844 46.49010040627619844, 6.55950172542713084 46.48890379920126747)) and surface > 1000000
and parameters.format == "LAS"

Critères d’acceptation

ID Critère
19672-1 Il est possible d'utiliser des parenthèses dans les règles pour changer l'ordre de prise en compte des opérateurs booléens [and|or]
19672-2 https://www.asitvd.ch/v5/restricted_access/file.txt

Budget : 8441

Supporter la sélection des périmètres indirects (prédéfinis) [redmine:16635]

Original author : Yves Blatti

Supporter la sélection des périmètres indirects (prédéfinis).

Exemple : sélectionner une commune, ou une grille kilométrique sur viageo.ch. Il faudrait alors, soit :

  • recevoir un identifiant et le faire correspondre à une géométrie côté Extract
  • chercher la ou les géométrie côté serveur dans un référentiel (viageo ici) et transmettre la géométrie à Extract comme aujourd'hui.

Ajouter la possibilité de modifier les fichiers (avant export) [redmine:16831]

Original author : Yves Blatti

  • L'objectif est de pouvoir, ajouter et supprimer des fichiers lorsque l'élément est dans un statut arrêté (ERROR, STANDBY, EXPORT_FAIL)
  • Il est impossible d'ajouter des fichiers si le traitement est terminé ou annulé
  • Les modifications se font dans l'écran de détail d'un élément @https://[extractserver]/extract/requests/[id]@
  • Suppression :
    ** La suppression est proposée par une icône de poubelle, rouge, à droite du lien de téléchargement du fichier (classe @fa-trash@)
    ** La suppression est précédée d'une confirmation (Message : Êtes-vous sûr de vouloir supprimer le fichier "[nom_du_fichier]"), en modal, comme les autres confirmations de l'application.
    ** Si la suppression est confirmée, le fichier est supprimé immédiatement (AJAX, ou rechargement de la même page)
    ** Si la suppression est annulée, aucune action n'est faite et l'utilisateur reste sur la même page.
    ** Les fichiers sont supprimés du dossier temporaire de l'élément (output)
  • Ajout :
    ** Un bouton permet de parcourir pour uploader +1 ou plusieurs fichiers+
    ** Texte du bouton en français : "Ajouter des fichiers…"
    ** Au clic, la fenêtre de sélection des fichiers du navigateur est ouverte
    ** L'utilisateur sélectionne un ou plusieurs fichiers
    ** S'il confirme (bouton ouvrir de la modal du navigateur), les fichiers sont immédiatement uploadés (AJAX, ou rechargement de la même page)
    ** S'il annule, aucune action n'est faite et l'utilisateur reste sur la même page.
    ** Si un ou plusieurs fichiers sont déjà présent pour cet élément de requête, un message informe que les fichiers de même nom seront écrasés.
    "Les fichiers portant le même nom seront écrasés"
    ** Lors de l'upload, les fichiers dont le nom contient des caractères dangereux, ou pouvant être mal supportés par les OS (‘ ´ » / \ : ; etc...) sont renommés (nettoyés). Voir réalisation de EM91/archivage dans la v1.0. Exemple : @l'apéro.zip@ -> @l_apero.zip@
    ** Les fichiers sont uploadés dans le dossier temporaire de l'élément (output)

Maquettes

Ajout de fichiers et suppression :

16831_add_or_delete_files

Sélection pour ajout de plusieurs fichiers possible

16831_add_multiple_files

Ajout de fichiers, si aucun fichier présent :

16831_add_files_no_files_present

Confirmation de la suppression :

16831_delete-file-confirm

Pas de modification possible si traitement en cours (état actif) ou si traitement terminé/annulé

16831_process_complete

Critères d’acceptation

|.ID|.Critère|
|16831-1|Il est possible d'ajouter un ou plusieurs fichiers (en une sélection)|
|16831-2|Après l'ajout, on retrouve la même page, l'élément reste dans le même statut|
|16831-3|Si on upload un fichier avec des caractères spécieux, il est renommé (ex: @l'apéro.zip@ -> @l_apero.zip@)|
|16831-4|Si un fichier présent possède le même nom (après renommage, s'il a eu lieu), il est écrasé|
|16831-5|le champ d'upload est représenté par un bouton bootstrap (et non le champ par défaut du browser)|
|16831-6|Si la sélection est validée (écran de sélection du navigateur, 16831_add_multiple_files.png), les fichiers sont immédiatement uploadés|
|16831-7|Il est possible de supprimer un fichier (après confirmation)|
|16831-8|Si la suppression est annulée à la modale, aucune modification n'est apportée, et l'utilisateur reste sur la page|


Budget : 2948

Carte paramétrable [redmine:17219]

Original author : Xavier Mérour

La vue "request" affiche par défaut une carte avec le service OSM comme fond de plan.
L'objectif est de permettre à l'administrateur de surcharger la carte par défaut s'il le désire, avec un ou des fonds de plan, et éventuellement une ou des couches à superposer.

  • Dans la configuration (@https:///extract/parameters@)
    ** Ajouter un bloc de paramétrage "Carte personnalisée, laisser vide pour utiliser la carte par défaut"
    ** Les paramètres sont :
    *** Fonds de plan (code)
    **** Type : textarea
    **** Contenu : Tableau de definitions de layers OpenLayers 4 (Code Javascript) [ou vide pour conserver le fond par défaut]
    **** Éviter la correction et complétion dans le champ ( attributs de textarea : autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" )
    **** Tous types de couches OpenLayers doivent être supportés (WMS, WMTS, etc...)
    *** Couches supplémentaires (code)
    **** Type : textarea
    **** Contenu : Tableau de definitions de layers OpenLayers 4 (Code Javascript)
    **** Éviter la correction et complétion dans le champ ( attributs de textarea : autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" )
    **** Tous types de couches OpenLayers doivent être supportés (WMS, WMTS, etc...)
    *** Code de projection
    **** Type : texte
    **** Contenu : Code de projection compatible Openlayers [ou vide si fond par défaut]
    par exemple : EPSG:21781
    ** Un tooltip d'aide explique le fonctionnement, la syntaxe et présente un exemple de configuration (complet et fonctionnel pour les 3 champs)
  • Dans l'écran de détail de l'élément (@https:///extract/requests/@)
    ** La carte possède un module de choix de couches (LayerSwitcher)
    *** Un fond de carte peut être affiché à la fois (radio button), par défaut, le premier
    *** 0 à n couches peuvent être superposées à la carte, aucune par défaut
  • Le système doit supporter différentes projection (embarquées ou chargées dynamiquement, epsg.io, spatialreference.org ?)

Proposition technique pour le layer switcher : https://github.com/walkermatt/ol-layerswitcher

Maquettes

17219-config

17219-map-layerswitcher

17219-map-layerswitcher-hover

Critères d’acceptation

|.ID|.Critère|
|17219-1|Si aucun fichier @map.custom.js@ n'est présent, la carte de base s'affiche (la carte actuelle, OSM, avec @map.js@) |
|17219-2|Il est possible de choisir la projection de la carte (par exemple : EPSG:2056 ou EPSG:21781)|
|17219-3|Il est possible d'afficher des fonds de plans WMS (1) et WMTS (1)|
|17219-4|Il est possible d'afficher des couches WMS (1) et WMTS (1)|

Notes:
(1) à démontrer avec au minimum :
-ol.layer.Image avec ol.source.ImageWMS
-ol.layer.Tile avec ol.source.TileWMS
-ol.layer.Tile avec ol.source.WMTS


Budget : 2948

GUI à améliorer [redmine:17220]

Original author : Xavier Mérour

Il y a plusieurs petits détails qui ne suivent pas notre maquette et qui devraient être corrigés :

  • les textes du menu ne sont pas centrés verticalement
  • plusieurs tableaux ont des lignes verticales de séparations (à enlever pour alléger visuellement)
  • les titres de colonnes de tableau cliquables pour trier sont suffisants, pas besoin des pictos à droite en plus (par contre conserver le picto qui indique sur quelle colonne le trie est actif)
  • sous "Traitements", la largeur de la 1ere colonne devrait être plus grande
  • sous "Configuration d'un connecteur", la partie haute (paramètres généraux du connecteur) est un peu chaotique.
  • sous "Configuration d'un connecteur", donner un peu d'air au tableau du bas (comme dans la maquette)
  • sur la page d'accueil, le datepicker a le sommet masqué lorsqu'il n'y a pas de demande en traitement

Erreur 500 lors de l'affichage d'une demande assignée à un opérateur qui n'existe plus [redmine:22570]

Original author : Rémi Bovard

Pour reproduire :

  • Se connecter avec compte U1

  • Créer un compte U2

  • Créer un traitement T1, avec U1 et U2 comme opérateurs

  • Faire une commande C1 qui arrive dans T1

  • Traiter commande C1

  • Supprimer U2

  • Afficher C1 dans la liste des demandes traitées

L'erreur suivante se produit :


Une erreur s'est produite lors de la dernière opération.

500 - Internal Server Error

Exception evaluating SpringEL expression: "user.systemUser" (requests/details:376)

Le problème est que U1 existe toujours et est toujours assigné à T1, donc il n'a plus accès au détails de C1 (err500).

Budget : 1860

Plugin d'archivage écrase les fichiers ayant le même nom [redmine:20294]

Original author : Rémi Bovard

Lorsque que les fichiers d'une commande avec plusieurs produits ont le même nom (p. ex. même script FME lancé 2x), seul le dernier est gardé dans le dossier d'archivage si le chemin n'est pas spécifique au produit.

Il faudrait au minimum ajouter l'information dans la tooltip d'aide du plugin pour éviter ces désagréments.

Plugin de tâche : batch/executable [redmine:22288]

Original author : Yves Blatti

Proposition lors de la rencontre du GU le 03.12.2018.

Un plugin de tâche pour exécuter des exécutables/batchs/scripts arbitraires.
Exemples d'utilisation :

  • ogr2ogr
  • composer/atlas QGIS
  • QGIS export DWG

A evaluer...

Télécharger le périmètre en DXF ou KML [redmine:22291]

Original author : Yves Blatti

Ajouter un bouton de téléchargement du périmètre de commande en DXF et en KML.
Le fichier DXF téléchargé doit être dans le SRS de la carte.

Maquettes

download_perimeter_2

Critères d’acceptation

|.ID|.Critère|
|22291-1|Un bouton propose le téléchargement du périmètre en KML|
|22291-2|Un bouton propose le téléchargement du périmètre en DXF|
|22291-3|Le fichier DXF est dans le SRS de la carte|
|22291-4|Les boutons sont aussi ajoutés à la carte si c'est une carte personnalisée (map.custom.js)|
|22291-5|Le fichier KML est en WGS84|


Note : une ancienne implémentation existe dans easySDI : https://forge.easysdi.org/projects/easysdi/repository/entry/branches/4.5.x/joomla/easysdi/com_easysdi_shop/src/site/views/order/tmpl/order.js

Budget : 2948

Les règles écrites sur plusieures lignes ne match pas [redmine:20432]

Original author : Yves Blatti

Les règles écrites sur plusieures lignes ne match pas.

Cet exemple ne match pas :


productguid=="e0a491cc-a8f3-3e64-0590-c534ce6b0144"
OR
productguid=="aaaaaaaa"

workaround : ajouter des espaces :


productguid=="e0a491cc-a8f3-3e64-0590-c534ce6b0144" 
 OR 
 productguid=="aaaaaaaa"

Je suppose qu'un petit @x.replaceAll("\r\n", " ")@ devrait résoudre le problème

Pouvoir cloner un traitement [redmine:17759]

Original author : Xavier Mérour

Pour faciliter la saisie des traitement, il faut pouvoir en copier un existant, avec ses tâches.

Comportement

  • L'administrateur clique sur le bouton dupliquer (class @fa-copy@) dans la liste des traitements
  • Un nouveau traitement est créé, il :
    ** Est nommé avec un suffixe : "<nom_original> - copie"
    ** Contient tous les opérateurs de l'original
    ** Contient une copie de toutes les tâches, dans le bon ordre et avec les paramètres originaux

Maquettes

clone_process

Critères d’acceptation

|.ID|.Critère|
|17759-1|Il est possible de cloner un traitement depuis la liste|
|17759-2|Toutes les tâches sont copiées dans le nouveau traitement|
|17759-3|Toutes les opérateurs sont copiées dans le nouveau traitement|
|17759-4|Le nouveau traitement porte le nom de l’orignal avec le suffixe " - copie"|

Budget : 2949

Licences FME lors de multiples traitements identiques [redmine:22022]

Original author : Rémi Bovard

Lors d'une commande avec plusieurs produits (5 dans ce cas), qui arrivent dans le même traitement EXTRACT, une erreur FME de licence se produit :


FME license system failure: The maximum number of concurrent FME instances specified in the license has been reached (-508)

Après quelques tests, j'ai vraiment l'impression que le problème est que les 5 process FME sont appelés au même moment exact car si je relance les traitements manuellement (avec un intervalle d'environ 1 seconde - le temps de cliquer), FME fonctionne bien et les traitement tournent simultanément.

Pour info nous avons une licence fixe.

A surveiller.


Origine du problème

FME Desktop limite le d'instances de fme.exe en exécution parallèle à 8. [1]
Les scripts que tu utilise avec EXTRACT utilisent un WorkspaceRunner, ce qui lance une fme.exe supplémentaire (donc 2 processus par extraction). Ceci explique l'erreur dès 5 produits commandés en parallèle.

Ceci peut donc se produire lorsque EXTRACT reçoit :

  • 8 produits avec un script simple

  • 4 produits avec un script + 1 WorkspaceRunner

  • 2 produits avec un script + 2 WorkspaceRunner

  • etc...

Solution 1 : Passer tout EXTARCT en "séquentiel".
Empêcher toute exécution en parallèle dans EXTRACT.
Changement d'architecture du logiciel. Règle le problème mais limite le fonctionnement d'EXTRACT... bof

Solution 2 : Passer le plugin FME Desktop en "séquentiel".
Empêcher toute exécution en parallèle d'un plugin FME Desktop.
Changement d'architecture du logiciel. Un plugin doit pourvoir se "déclarer" comme n'étant pas parallèle afin que l'orchestrateur n'en lance qu'un à la fois. Règle le problème pour FME Desktop et peut être d'autres à venir. NB : il ne sera plus jamais possible d'avoir deux extraction simultanées.

Solution 3 : Compter le nombre de FME en cours d’exécution.
Compter le nombre de fme.exe en cours d’exécution. Attendre s'il n y en a pas assez de "libre" (8 - LeNombreEnCours - LeNombrePourLeScript).
Changement uniquement dans le Plugin FME Desktop. Il faut:

  • un paramètre de plugin supplémentaire (car le nombre de FME.exe lancé par un script peut varier)
  • compter le nombre de tâches FME en cours, lancer le traitement uniquement si la limite n'est pas atteinte, attendre dans le cas contraire.

Il faudra faire attention à quelques points:

  • Compter les processus depuis java se fait par un appel système, donc différent pour Windows et Linux [2]
  • Suivant l'utilisateur connecté, est-ce que toutes le instances FME seront visibles (autre utilisateur...) ?

C'est la solution qui, il me semble, répond le plus précisément au problème

solution3

Maquette config :
solution3_config


Notes:
[1] : https://knowledge.safe.com/articles/139/how-many-concurrent-fme-processes-can-i-run-at-my.html
[2] : https://crunchify.com/how-to-get-a-list-of-current-open-processes-with-java/

Les assets sont conservés en cache browser lors d'une mise à jour [redmine:22380]

Original author : Yves Blatti

Les assets sont conservés en cache browser lors d'une mise à jour
Il faut versionner les assets


<link href="/extract/lib/bootstrap/dist/css/bootstrap.min.uneVersionGeneree.css" rel="stylesheet" type="text/css" />

ou éventuellement

<link href="/extract/lib/bootstrap/dist/css/bootstrap.min.css?v=uneVersionGeneree" rel="stylesheet" type="text/css" />

La version peut être aléatoire, provenir de la révision, ou être un hash du fichier.

Budget : 430

Prise en charge des opérateurs IN et NOT IN dans les règles [redmine:19613]

Original author : Yves Blatti

Pour la permettre la comparaison d'une variable à plusieurs valeurs ajouter la prise en charge des opérateurs @in@ et @Not IN@ (équivalent SQL)

exemple 1 :

@productguid IN ("70370f51-ec38-4eeb-b0e2-5489ffa03969","65939378-46bf-4d76-a74c-a01a94ad8381","196")@

cette règle doit actuellement être écrite ainsi:

@productguid == "70370f51-ec38-4eeb-b0e2-5489ffa03969" OR productguid == "65939378-46bf-4d76-a74c-a01a94ad8381" OR productguid == "196")@

exemple 2 :

@productguid NOT IN ("70370f51-ec38-4eeb-b0e2-5489ffa03969","65939378-46bf-4d76-a74c-a01a94ad8381","196")@

cette règle doit actuellement être écrite ainsi:

@productguid != "70370f51-ec38-4eeb-b0e2-5489ffa03969" AND productguid != "65939378-46bf-4d76-a74c-a01a94ad8381" AND productguid != "196")@

Ces opérateurs sont compatibles avec les autres (attributaires et géographiques) et peuvent être combinés avec des AND ou OR.

exemple 3 :

@productguid IN ("70370f51-ec38-4eeb-b0e2-5489ffa03969","196") AND surface < 10000@

Critères d’acceptation

|.ID|.Critère|
|19613-1|Il est possible de comparer une variable à 1 à n valeurs avec l'opérateur IN ou NOT IN|
|19613-2|Une règle contenant l'opérateur IN ou NOT IN peut contenir d'autres filtres|
|19613-3|Les opérateurs IN et NOT IN sont insensibles à la casse (IN = In = in)|
|19613-4|L'aide en ligne de saisie des règles est adaptée aux nouveaux opérateurs IN et NOT IN, avec un exemple|

Budget : 2948

Limite de taille des fichiers à l'upload [redmine:21501]

Original author : Daniel Savary

Je peux ajouter un seul fichier à la fois.
Lorsque je souhaite ajouter plusieurs fichiers (sélection) en une seule fois.
J'obtiens l'erreur suivante : ERROR serveur 500

Origine : limite à l'upload.

Afficher version EXTRACT + lien vers easysdi.org [redmine:17762]

Original author : Xavier Mérour

  • Afin de faciliter le support et la communication il faut afficher la version actuellement installée (complète), le site du projet et la licence GPL.
  • Ce contenu doit être ajouté dans un nouveau panel sur la page de configuration, nommé "À propos d'EXTRACT"
  • Contenu : "@extract fait partie du projet open source easySDI et est distribué sous licence GPLv3.
    Version installée : {fullversion}@"

Maquette

17762-about-extract

|.ID|.Critère|
|17762-1|La version installée, un lien vers easysdi.org et la licence sont affichés dans un panel sur la page de config|


Budget : 727

Ajout documentation paramètres pour connecteur FME [redmine:18810]

Original author : Xavier Mérour

  • Modification des plugins +FME+ et +FME Server+

  • Ajouter un tooltip d'aide (comme pour les autres plugins)
    18810_access_tooltip

  • Le contenu de l'aide doit aider un administrateur d'EXTRACT à créer un script FME compatible avec EXTRACT

  • Il faut y décrire les variables passées, leur nom, leur type et des données d'exemple
    ** Perimeter (et son encodage en WKT WGS84)
    ** Product (string, ID externe du produit)
    ** FolderOut (dossier de sortie utilisé pour FME desktop)
    ** Parameters (décrire leur encodage et l'aspect dynamique)

  • Expliquer le retour de donner (différence entre FME desktop et serveur)

  • pour FME Server -> spécifier qu'il faut utiliser le service "Data Download" et non "Job Submitter"

Paramètres publics à utiliser dans un script FME

18810-FME_published_parameters

Critères d’acceptation

|.ID|.Critère|
|18810-1|Le plugin FME desktop contient une aide en ligne pour aider l'administrateur|
|18810-2|Le plugin FME server contient une aide en ligne pour aider l'administrateur|


Budget : 1454

Plugin d'annulation [redmine:16683]

Original author : Yves Blatti

  • Nouveau plugin de tâche
  • Lors de l’exécution de la tâche :
    ** La tâche passe l'élément en "annulé" (rejected = true)
    ** La tâche définit la remarque avec celle configurée dans l'interface (nommée "Raison de l'annulation" dans l'interface)
    ** Si une remarque est présente avant l'étape d'annulation, elle est écrasée par la raison de l'annulation
  • Exemple d'utilisation : pour annulation d'un élément hors zone
    ** 1) Créer un nouveau traitement, lui donner un nom (par ex: "Annulation directe")
    ** 2) Ajouter le plugin d'annulation (souvent, le plugin d'annulation sera le seul dans le traitement)
    ** 3) Définir la Raison de l'annulation : "Désolé, nous n'avons pas de donnée sur cette zone"
    ** 4) Ajouter une règle qui pointe sur ce traitement, par exemple règle géographique "Si le périmètre est hors de la zone"
    @perimeter disjoint POLYGON((6.82 46.39,6.92 46.39,6.92 46.19,6.92 46.19,6.82 46.39))@
    ** 5) Placer cette règle au sommet, pour qu'elle match en premier si besoin
    ** Dès maintenant, toute commande hors zone sera annulée avec comme remarque la Raison de l'annulation définie dans l'interface

Interface de configuration

  • Nom du plugin en français : Annulation
  • Description dans le bloc à drag/dropper : "Annule le traitement et définit la remarque pour le client"
  • Classe d’icône : "fa-ban"
  • Paramètres :
    ** "Raison de l'annulation" , type: textarea, obligatoire
  • Message d'aide (tooltip)
    ** "Ce plugin permet d'annuler le traitement d'un élément et de définit inscrit la raison de l'annulation dans la remarque au client.
    Si une remarque existe elle sera écrasée par la raison de l'annulation."

Maquettes

Nouveau bloc à drag dropper + bloc dans le processus de traitement :

16683-plugin-annulation-full

Tooltip d'aide :

16683-plugin-annulation-tooltip

Critères d’acceptation

ID Critère
16683-1 Le paramètre “Remarque sur l’annulation” est obligatoire, il est impossible de sauver le traitement sans
16683-2 Après la tâche, l’élément est en statut annulé, ceci est visible dans l’interface : element-rejected
16683-3 Après la tâche, lorsque l’élément est retourné au client (suite à l’export), il est annulé avec la remarque contient la raison de l’annulation(plateforme asitvd.ch) : element-rejected_asitvd
16683-4 Si une remarque était définie dans une étape précédente du traitement, elle est écrasée
16683-5 Le comportement après passage dans le plugin d’annulation est identique à une annulation manuelle : la suite du traitement est ignorée et ceci est visible dans l’historique

Budget : 2908

Gérer un périmètre incorrect en entrée [redmine:17755]

Original author : Xavier Mérour

Modification sur le plugin easySDI v4.

Si le user commande une donnée avec un périmètre prédéfini (parcelle, commune...) sur une plateforme easySDI, EXTRACT n'est pas en mesure de gérer l'extraction car il n'y a pas de géométrie associée (uniquement un ID de géométrie --> cf. #133). Il en résulte:

  • Un affichage incorrect dans les interfaces d'EXTRACT (pas de carte, surface etc..)
  • Un crash des tâches qui attendent un WKT de géométrie (traitement FME, etc)

La distinction se fait sur l'attribut "@type@" de la balise "@perimeter@"

Périmètre OK:

Type de périmètre : "coordinates" -> OK peut être importé sans erreurs


<code class="xml">
    <sdi:perimeter type="coordinates" id="1" alias="freeperimeter" guid="f0d48c8e-ad5a-09f4-d1b0-dce7dfa544a9">
      <sdi:surface unit="m2">1874219.75000000000000000000</sdi:surface>
      <sdi:contents>
        <sdi:content>POLYGON((6.556053540245442 46.5406858665773,6.564736227940287 46.552776494518355,6.579339266387595 46.54778277789993,6.57065411715828 46.53569326277157,6.556053540245442 46.5406858665773))</sdi:content>
      </sdi:contents>
    </sdi:perimeter>

Périmètre KO:

Type de périmètre : "values" -> KO doit être importé en erreur


<code class="xml">
  <sdi:perimeter type="values" id="4" alias="COMMUNE" guid="71ae448a-cfe4-f1f4-f597-ce2d7a0702bb">
     <sdi:surface unit="m2">8568479.00000000000000000000</sdi:surface>
      <sdi:contents>
        <sdi:content>348</sdi:content>
        <sdi:content>312</sdi:content>
      </sdi:contents>
  </sdi:perimeter>

Comportement attendu :

  • Un élément sans périmètre de type="coordinates" devrait être importé en erreur, avec un message clair et la possibilité de l'annuler (ou de le supprimer si connecté en tant qu'administrateur, au bas de l'écran, déjà implémenté).
  • Message à l'utilisateur : "Cet élément n'a pas de périmètre géographique, il ne pourra pas être traité."

Budget : 1090

Ajouter des variables à passer à FME (Desktop et Server) [redmine:20315]

Original author : Yves Blatti

Afin de pouvoir effectuer des traitement FME plus complexes, passer les paramètres supplémentaires suivants pour les traitement FME Desktop et FME Server :

  • N° De commande externe (p_orderlabel)
  • N° De commande interne (id_request)
  • GUID du client (!non stocké su v1.1 p_clientguid) (note 1)
  • GUID de l'organisme (!non stocké su v1.1 p_organismguid) (note 2)

Les GUIDs (client, organisme, produit) et l'DI interne de la requête sont affichés dans le panneau administration, au fond de l'écran, avec leur code.

Maquettes

Panneau d'administration

administration_panel_infos_fixed

Critères d’acceptation

|.ID|.Critère|
|20315-1|Il est possible d'utiliser les 4 nouveaux paramètres dans FME Desktop|
|20315-2|Il est possible d'utiliser les 4 nouveaux paramètres dans FME Server|
|20315-3|La rétrocompatibilité doit être assurée: pas besoin de refaire les scripts FME existants pour passer à EXTRACT 1.2|
|20315-4|Les 3 GUIDs (client, organisme et produit) et l'ID interne sont affichés dans la zone d'admin au fond de la page détail|


Notes :

  1. Doc service XML : https://forge.easysdi.org/projects/easysdi/wiki/4_shop_rest#Outputs : chemin XML /sdi:orders/sdi:order/sdi:client/@Guid pour easySDI
  2. Doc service XML : https://forge.easysdi.org/projects/easysdi/wiki/4_shop_rest#Outputs : chemin XML /sdi:orders/sdi:order/sdi:client/sdi:organism/@Guid pour easySDI

Budget : 2948

Télécharger tous les fichiers lors de la validation [redmine:21289]

Original author : Rémi Bovard

Jusqu'à la v1.1 lors de la validation d'une commande, chaque fichier peut être téléchargé manuellement en cliquant dessus.

Pour améliorer le confort de la personne qui valide la commande, s'il y a plus d'un fichier, un bouton "Télécharger tous les fichiers" permet de télécharger un zip comprenant tous les fichiers, en conservant l'arborescence.

Maquette

download_all_files

Critères d’acceptation

|.ID|.Critère|
|21289-1|S'il y a plus d'un fichier à télécharger, proposer à l'opérateur un bouton "Télécharger tous les fichiers"|
|21289-2|Lors du téléchargement groupé, les fichiers sont zippés|
|21289-3|Dans le zip, l’arborescence des fichiers (depuis le dossier output) est conservée|
|21289-4|Le nom du fichier est "<request_id>.zip", par exemple "1657.zip"|

Budget : 1090

Lorsqu'un périmètre dépasse 4000 caractères la requête n'est pas importée. [redmine:21582]

Original author : Yves Blatti

Lorsqu’un périmètre dépasse 4000 caractères la requête n’est pas importée.

Il faudrait utiliser un type TEXT dans postgres

LOG :


<code class="php">
16:24:33.866 78300921 [pool-8-thread-1] ERROR o.e.e.b.w.ImportedRequestsWriter - Could not save the imported requests.
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:765)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:734)
	at sun.reflect.GeneratedMethodAccessor455.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
	at com.sun.proxy.$Proxy284.commit(Unknown Source)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
	at com.sun.proxy.$Proxy268.save(Unknown Source)
	at org.easysdi.extract.batch.writer.ImportedRequestsWriter.write(ImportedRequestsWriter.java:129)
	at org.easysdi.extract.orchestrator.runners.CommandImportJobRunner.run(CommandImportJobRunner.java:128)
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: javax.persistence.RollbackException: Error while committing the transaction
	at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:87)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
	... 36 common frames omitted
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [org.easysdi.extract.domain.Request] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
	ConstraintViolationImpl{interpolatedMessage='size must be between 0 and 4000', propertyPath=perimeter, rootBeanClass=class org.easysdi.extract.domain.Request, messageTemplate='{javax.validation.constraints.Size.message}'}
]
	at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)
	at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:78)
	at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:205)
	at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:82)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
	at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
	... 37 common frames omitted
16:24:33.866 78300921 [pool-8-thread-1] ERROR o.e.e.o.r.CommandImportJobRunner - Could not process the product "248114 - R?seau d'assainissement de Dagobah".
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:765)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:734)
	at sun.reflect.GeneratedMethodAccessor455.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
	at com.sun.proxy.$Proxy284.commit(Unknown Source)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
	at com.sun.proxy.$Proxy268.save(Unknown Source)
	at org.easysdi.extract.batch.writer.ImportedRequestsWriter.write(ImportedRequestsWriter.java:129)
	at org.easysdi.extract.orchestrator.runners.CommandImportJobRunner.run(CommandImportJobRunner.java:128)
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: javax.persistence.RollbackException: Error while committing the transaction
	at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:87)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
	... 36 common frames omitted
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [org.easysdi.extract.domain.Request] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
	ConstraintViolationImpl{interpolatedMessage='size must be between 0 and 4000', propertyPath=perimeter, rootBeanClass=class org.easysdi.extract.domain.Request, messageTemplate='{javax.validation.constraints.Size.message}'}
]
	at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)
	at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:78)
	at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:205)
	at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:82)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
	at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
	... 37 common frames omitted

Le problème est gênant avec le connecteur easySDI v4, car si la transaction HTTP se termine bien le service easySDI estime que tout OK, et la requête n'est plus présentée.
Elle va donc "disparaitre" si ce cas se présente.

Aujourd'hui (v1.0 et v1.1-BETA2) la colonne est un varchar(4000)

Ma proposition :
Forcer la colonne p_perimeter au type TEXT dans Hibernate.
On devrait faire pareil pour les règles.

Passer la définition des colone en TEXT, serait supporté par :

  • postgreSQL : (taille "Illimitée") c'est notre DB cible, donc parfait
  • MySQL : 2^16 Bytes (65536 B)
  • MSSQL : 2^31-1 Bytes (2147483647 B)
  • Oracle : 4000 Bytes (pas de d'amélioration, mais compatible)

Dans le code il faudrait :

Remplacer dans java\easysdi\extract\src\main\java\org\easysdi\extract\domain\Request.java


<code class="java">
    /**
     * The WKT geometry of the geographical area for this order.
     */
    @Size(max = 4000)
    @Column(name = "p_perimeter", length = 4000)
    private String perimeter;

par :


<code class="java">
     /**
     * The WKT geometry of the geographical area for this order.
     */
    @Column(name = "p_perimeter", columnDefinition = "text")
    private String perimeter;

et pour les règles :

Remplacer dans java\easysdi\extract\src\main\java\org\easysdi\extract\domain\Rule.java


<code class="java">
     /**
     * The criteria that the data item request must satisfy.
     */
    @Size(max = 4000)
    @Column(name = "rule", length = 4000)
    private String rule;

par :


<code class="java">
     /**
     * The criteria that the data item request must satisfy.
     */
    @Column(name = "rule", columnDefinition = "text")
    private String rule;

Ensuite afin que les mises à jour roulent il faut changer les types de colonnes (car Hibernate ne fait pas d'alter table tout seul).
Je propose que l'on mette à jour les fichier SQL post-installation afin qu'il soit idempotent (on peut l'applique plusieurs fois) et qu'il soit à executer après chaque installation ET mise à jour.
Contenu proposé :


<code class="sql">
-- PROCESSES_USERS Table

ALTER TABLE processes_users
  DROP CONSTRAINT fk_processes_users_process;

ALTER TABLE processes_users
  ADD CONSTRAINT fk_processes_users_process FOREIGN KEY (id_process)
      REFERENCES processes (id_process) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;

DROP INDEX IF EXISTS idx_processes_users_process;

CREATE INDEX idx_processes_users_process
  ON processes_users (id_process);

ALTER TABLE processes_users
  DROP CONSTRAINT fk_processes_users_user;

ALTER TABLE processes_users
  ADD CONSTRAINT fk_processes_users_user FOREIGN KEY (id_user)
      REFERENCES users (id_user) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;
	  
DROP INDEX IF EXISTS idx_processes_users_user;

CREATE INDEX idx_processes_users_user
  ON processes_users (id_user);


-- REQUESTS Table

ALTER TABLE requests
  DROP CONSTRAINT fk_request_connector;

ALTER TABLE requests
  ADD CONSTRAINT fk_request_connector FOREIGN KEY (id_connector)
      REFERENCES connectors (id_connector) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE SET NULL;

ALTER TABLE requests
  DROP CONSTRAINT fk_request_process;

ALTER TABLE requests
  ADD CONSTRAINT fk_request_process FOREIGN KEY (id_process)
      REFERENCES processes (id_process) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE SET NULL;
	  
ALTER TABLE requests ALTER COLUMN p_perimeter TYPE TEXT;


-- REQUEST_HISTORY Table

ALTER TABLE request_history
  DROP CONSTRAINT fk_request_history_request;

ALTER TABLE request_history
  ADD CONSTRAINT fk_request_history_request FOREIGN KEY (id_request)
      REFERENCES requests (id_request) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;
      
ALTER TABLE request_history
  DROP CONSTRAINT fk_request_history_user;

ALTER TABLE request_history
  ADD CONSTRAINT fk_request_history_user FOREIGN KEY (id_user)
      REFERENCES users (id_user) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE SET NULL;


-- RULES Table

ALTER TABLE rules
  DROP CONSTRAINT fk_rule_connector;

ALTER TABLE rules
  ADD CONSTRAINT fk_rule_connector FOREIGN KEY (id_connector)
      REFERENCES connectors (id_connector) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;
            
ALTER TABLE rules
  DROP CONSTRAINT fk_rule_process;

ALTER TABLE rules
  ADD CONSTRAINT fk_rule_process FOREIGN KEY (id_process)
      REFERENCES processes (id_process) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;

ALTER TABLE rules ALTER COLUMN rule TYPE TEXT;

-- TASKS Table

ALTER TABLE tasks
  DROP CONSTRAINT fk_task_process;

ALTER TABLE tasks
  ADD CONSTRAINT fk_task_process FOREIGN KEY (id_process)
      REFERENCES processes (id_process) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE CASCADE;

Ajout du filtre à l'import, par liste de produits (connecteur sdiv4) [redmine:16448]

Original author : Yves Blatti

Cas d'exemple: la ville de Lausanne va avoir un EXTRACT au SCC, mais le même compte sera utilisé par Alpiq (SEL 125KV...)
Il faudra que chacun des EXTRACT ne consomme le service QUE pour SES produits.

<!-- getOrdersByGuids with parameter guids -->
<?xml version="1.0"?>
<sdi:parameters xmlns:sdi="http://www.easysdi.org/2011/sdi" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation="http://www.easysdi.org/2011/sdi/getordersparameters.xsd" 
            xsi:type="sdi:getOrdersByGuids">

    <sdi:guids>
        <sdi:guid>f655e481-39d1-3224-1db5-665c402b2869</sdi:guid>
        <sdi:guid>d8d903d2-8117-89c4-bd9d-6c00e137ba44</sdi:guid>
    </sdi:guids>

</sdi:parameters>

Lien vers fichier archivé [redmine:17758]

Original author : Xavier Mérour

Modification du plugin d'archivage.

  • Une fois un traitement fini et le résultat exporté, le fichier résultat n'est plus disponible dans EXTRACT (fichiers temporaires supprimés).
  • pour permettre à l'utilisateur de retrouver les fichier si une tâche d'archivage a été utilisée, il faut afficher le chemin d'archive dans le message de retour dans le log
  • Le message français remplace le OK, par "@emplacement : [complete_archive_path]@"

Maquette

Actuellement :

17758_before

Comportement désiré :

17758_after2

Critères d’acceptation

|.ID|.Critère|
|17758-1|Si une tâche d'archivage se termine correctement, le chemin d'archivage est affiché dans le log|
|17758-2|Le chemin affiché est celui effectif (une fois les variables remplacées)|

Budget : 1090

Tâche "notification email" [redmine:18705]

Original author : Xavier Mérour

  • Nouveau plugin de traitement.
  • Titre français : "Notification email"
  • Description : Envoie une notification email personnalisable.
  • Classe icône : fa-envelope-o
  • Paramètres :
    ** "Emails de destinataires, séparés par des points-virgules", email (nouveau type), obligatoire
    ** "Objet du message", text, obligatoire
    ** "Contenu du message", textarea, obligatoire
  • Tooltip d'aide :

Le plugin de notification email permet d'envoyer un message à un ou plusieurs destinataires.

Les emails des destinataires doivent être séparés par des points-virgules.

L'objet et le contenu du message peuvent contenir des variables qui seront remplacées par les propriétés de la requête. Les propriétés dynamiques autorisées sont les suivantes :

    orderLabel
    orderGuid
    productGuid
    productLabel
    startDate
    organism
    client

Exemple d'objet : "Nouvelle demande pour le produit {productLabel}, client {client}"

Exemple de contenu : "Le client {client} (organism) a commandé le produit {productLabel}
                      N° de commande : {orderLabel}
                      Date de traitement : {startDate}
                      Les données livrées ont été archivées vers : /home/data/extract/archive/{orderLabel}/{productLabel}"

  • Lorsque qu'un élément arrive à cette tâche de traitement, le système envoie un mail à chaque destinataire (chacun un mail, un destinataire ne voit pas les autres).
  • Le texte de l'objet et du message sont ceux définit dans le paramétrage de la tâche.
  • Le texte de l'objet et du message peuvent contenir des variables qui seront remplacées (voir plugin d'archivage).
  • Les variables pouvant être utilisées sont :
    ** orderLabel
    ** orderGuid
    ** productGuid
    ** productLabel
    ** startDate
    ** organism
    ** client
  • La liste des destinataires dans le paramétrage demande a ce que les emails soient séparés par des points-virgules
  • Lors de l’exécution de la tâche, le split de la liste de destinataires se fait sur virgule ou point-virgule ("," , ";"), les email sont ensuite nettoyés par trim (espaces en début et fin, tabs, retours de chariots... [\s])
  • Les paramètres d'envoi proviennent de la configuration système (expéditeur, conf smtp, etc)

Maquettes

18705-email-notification

Critères d’acceptation

|.ID|.Critère|
|18705-1|Lorsqu'un élément est traité par ce plugin, un message est envoyé à chaque destinataire|
|18705-2|Les emails sont individuels, un destinataire ne voit pas les autres|
|18705-3|Les messages peuvent contenir des variables, qui seront remplacées par les valeurs lors de l’exécution|
|18705-4|La configuration email et SMTP provient de la configuration système d'EXTRACT (@https:///extract/parameters@)|
|18705-5|La liste des destinataires est splitée par virgule ou point-virgule et nettoyée de caractères invisibles. Par exemple: "@technique@asitvd, [email protected] ; [email protected]@" est valable pour 3 adresses.|

Budget : 2908

Notifications optionnelles dans le compte utilisateur [redmine:22286]

Original author : Rémi Bovard

Ajouter la possibilité de désactiver les notifications de traitement d'un utilisateur.

Cet utilisateur ne recevra plus de notifications de traitement et d'erreurs de traitement.

Maquettes

Edition du compte

disable_emails

Critères d’acceptation

|.ID|.Critère|
|22286-1|Si les notifications sont actives, le comportement est le même que sur la version 1.1|
|22286-2|Si les notifications sont désactivées, aucune notification de traitement, ou erreur de traitement n'est envoyée à l'utilisateur|
|22286-3|Si le compte est administrateur et ses notifications désactivées, il continuera a recevoir les notifications admin (erreur import, export, ...)|
|22286-4|Les messages système (comme la réinitialisation de mot de passe) sont toujours envoyées|
|22286-5|Label "Actif ?" changé pour "Utilisateur actif"|

Budget : 1817

Suppression impossible de certaines requêtes (fonction admin) [redmine:21408]

Original author : Yves Blatti

Remonté par RM à Morges:

la fonction admin de suppression définitive d’une requête ne fonctionne pas s’il n’y a pas de dossier de données pour la demande.

Il semble que dans certains cas, une requête n’a pas encore de dossier, ou qu’il a été supprimé.
Il faudrait que l’administrateur puisse supprimer cette requête même s’il n’y a pas de dossier pour cette dernière.

Message à l’administrateur :


Une erreur s'est produite lors de la suppression de la demande. Veuillez réessayer plus tard.

Log :


08:59:45.704 239286 [http-nio-8080-exec-8] WARN  o.e.extract.utils.FileSystemUtils - Could not get data folders to purge for request 187.

Problème archivage réseau [redmine:17352]

Original author : Yves Blatti

Message d'erreur : L'archivage des fichiers a échoué : authentification réseau échoué. L'erreur systŠme 1219 s'est produite. Plusieurs connexions … un serveur ou … une ressource partag‚e par le mˆme utilisateur, en utilisant plus d'un nom utilisateur, ne sont pas autoris‚es. Supprimez toutes les connexions pr‚c‚dentes au serveur ou … la ressource partag‚e et recommencez. (-1)

A investiguer : ça sent la bricole, genre NET USE avec le user qui fait tourner Tomcat...

Mettre à jour la documentation [redmine:19785]

Original author : Xavier Mérour

Pour la version 1.1 d'EXTRACT, les modifications de documentation suivantes sont attendues :

  • si changements: mise à jour du Manuel d'installation et du Manuel d'exploitation
  • une procédure pour la mise à jour de la v1.0 à la v1.1
  • mettre à jour le DAT
  • une mise à jour du Manuel d'utilisation (nouvelle configuration, nouveaux plugins)
  • mise à jour de l'aide en ligne des différents éléments (plugins et configuration, dans les demandes)

Budget : 0

Pour la doc de mise à jour : spécifier de supprimer les dossiers [tomcat]/temp/*-extract*/ [redmine:21409]

Original author : Yves Blatti

À la mise à jour d’EXTRACT sur un Tomcat 9 (Windows), si le dossier temp de tomcat contient encore une ancienne version d’EXTRACT, le classloading des plugins échoue :


Message d'erreur : Plugin EMAIL not found.

Message d'erreur : Plugin REJECT not found.

Je propose que l’on ajoute dans la doc de mise à jour une ligne demandant la suppression des anciens dossiers EXTARCT de [tomcat-root]/temp. (Ils peuvent être nommées “0-extract”, “1-extract”, etc...)

Authentification avec un compte d'entreprise LDAP (Active Directory)

Objectif

Implémenter une authentification avec LDAP

Configuration (dans l'interface)

Dans les paramètres, sous le bloc Serveur SMTP, ajouter un nouveau bloc Authentification LDAP

Dans ce bloc, ajouter les champs suivants :

Champ Type Notes
Activer l'authentification avec LDAP Toggle Off par défaut
Si toggle activé, afficher les éléments ci-dessous
Serveurs LDAP* Texte Plusieurs valeurs possible
Type de chiffrage Toggle LDAPS (par défaut) ou STARTTLS
BASE_DN* Texte Plusieurs valeurs possibles
LDAP_QUERY pour lister les administrateurs* Texte
LDAP_QUERY pour lister les opérateurs* Texte
Activer la synchronisation avec LDAP Toggle Off par défaut
Si toggle activé, afficher les quatres champs ci-dessous
User LDAP* Texte
Password LDAP* Mot de passe
Fréquence de synchronisation avec LDAP, en heures Entier 24 par défaut
Synchroniser maintenant Bouton
Tester la connexion Bouton

*= champs obligatoires

Réglages

Configuration (dans le fichier de config du projet)

Le mapping entre les propriétés Extract et LDAP est défini dans le fichier de configuration global d'Extract.

Exemple :

Champ Extract Champ LDAP par défaut (pour AD)
Nom d'utilisateur cn
Login sAMAccountName
E-mail mail

Terminologie

Utilisateur local : Utilisateur dont la base Extract est la source d'authentification (comme aujourd'hui)
Utilisateur LDAP : Utilisateur dont un serveur LDAP est la source d'authentification
Utilisateur Extract : Tous les comptes utilisateurs dans la base Extract (local et LDAP)

Flux de connexion

Une fois l'authentification avec LDAP activée, des Utilisateurs LDAP peuvent se connecter à Extract avec leur identifiant et mot de passe LDAP.

Conditions pour Auth LDAP OK

  • Utilisateur actif
  • Password correct
  • Utilisateur dans un groupe Extract Admin ou Extract Opérateur dans l'annuaire LDAP

Création à la volée

Si un utilisateur se connecte à Extract avec LDAP et qu'il n'est pas présent dans la base locale, il est créé à la volée.

Flux de login

Note : Étape 2FA indiquée de manière simplifiée, décrite plus précisément ici : #255

Synchronisation LDAP

Il est possible d'activer la synchronisation LDAP.

Ces tâches sont les suivantes :

  • Créer des Utilisateurs LDAP (comptes existants dans l'annuaire LDAP, pas encore dans la base Extract)
  • Mettre à jour les propriétés des Utilisateurs LDAP (nom complet, email, rôle)
  • Désactiver les Utilisateurs LDAP dans la base Extract si ils sont désactivés ou supprimés de l'annuaire LDAP

Synchro

Règles qui s'appliquent aux Utilisateurs LDAP

Les Utilisateurs LDAP :

  • sont créés en base de données locale avec un type "LDAP"
  • ne peuvent pas changer leurs propriétés dans Extract (Nom complet, login, email, password)
  • ont un rôle en fonction du groupe dans lequel ils sont attribués sur l'annuaire LDAP
  • doivent être désactivés dans Extract s’ils sont désactivés ou supprimés dans l'annuaire LDAP

Les administrateurs

  • ne peuvent pas changer le rôle d'un Utilisateurs LDAP
  • ne peuvent pas désactiver / activer un Utilisateurs LDAP
  • ne peuvent pas changer les propriétés d'un Utilisateurs LDAP

Des Utilisateurs locaux peuvent coexister avec des Utilisateurs LDAP.

Routine de création d'un Utilisateur LDAP

Un Utilisateur LDAP est créé à partir des informations stockées dans LDAP.
Les propriétés de compte (login, Nom complet, email) sont automatiquement attribuées (correspondance entre les propriétés LDAP et Extract grâce au mapping configuré). Le rôle d’administrateur est attribué en fonction du groupe LDAP configuré.

Routine de mise à jour d'un Utilisateur LDAP

Met à jour l'Utilisateurs LDAP avec les dernières informations stockées sur l'annuaire LDAP (nom complet, email). Le rôle d’administrateur est attribué en fonction du groupe LDAP configuré.

Migration d'un Utilisateur local vers un Utilisateur LDAP

Dans la liste des Utilisateurs Extract, une nouvelle colonne "Type" permet d'identifier facilement le type d'utilisateur. Elle est visible seulement si l'authentification avec LDAP est activée.

Utilisateurs

Dans les détails d'un utilisateur, il est possible de migrer un Utilisateur local vers un Utilisateur LDAP si l'authentification avec LDAP est activée.

Les modifications suivantes sont apportées durant la migration :

  • L'utilisateur obtient le type "LDAP"
  • Les hashs liés à son authentification avec la base locale sont effacés

L'utilisateur pourra donc uniquement s'authentifier avec son username / password LDAP.

Cette opération n'est pas réversible et il n'est pas possible de migrer un Utilisateur LDAP vers un Utilisateur local.

Détails 1
Détails 2

Questions ouvertes

  • Les administrateurs doivent-ils être dans deux groupes dans l'annuaire LDAP ?
  • Le mécanisme de synchronisation devra supporter le déplacement ou le renommage des groupes AD. De ce fait devront-ils être identifiés par leur SID / GUID ?

Critères d'acceptation

Identifiant Description
131-1 Les administrateurs Extract peuvent en tout temps activer l'authentification avec LDAP
131-2 Une fois les paramètres renseignés, il doit être possible de tester la connexion
131-3 Une fonction doit pouvoir synchroniser immédiatement les Utilisateurs LDAP (création, mise à jour, suppression)
131-4 Les Utilisateurs LDAP doivent pouvoir se connecter à l'application en utilisant leur nom d'utilisateur et leur mot de passe LDAP.
131-5 Les propriétés d'un Utilisateur LDAP tels que le rôle ou le nom doivent provenir de l'annuaire LDAP
131-6 Les modifications apportées dans l'annuaire LDAP doivent être reflétées dans l'application grâce à la synchronisation.
131-7 Un administrateur peut migrer des Utilisateurs locaux (administrateurs et opérateurs) vers des Utilisateurs LDAP

Original author : Yves Blatti

  • Demande initiale de la Ville de Lausanne : Intégration de l’application avec un IDP en SAML 2.0 par Spring security
  • D'autres membres du groupe utilisateur sont intéressés par une intégration avec Microsoft Active Diretoy (AD) via LDAP

Mise à jour des librairies tierces [redmine:19794]

Original author : Xavier Mérour

Pour la version 1.1 d'EXTRACT nous attendons une mise à jour des librairies embarquées, spécifiquement les librairies sensibles (sécurité).
En particulier Spring (https://spring.io/blog/2018/04/05/multiple-cve-reports-published-for-the-spring-framework + https://spring.io/blog/2018/04/09/cve-2018-1275-address-partial-fix-for-cve-2018-1270)

À la livraison, merci de nous indiquer les changements effectués.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.