@mnemotix/weever-core v2.1.1
Weever
[TOC]
Tour d'horizon
Weever est un outil de documentation articulé autour d'une chaîne de traitement sémantique.
Dit autrement, on peut la voir comme une application Web permettant une manipulation simplifiée d'une base de donnée RDF basée sur l'ontologie générique de Mnémotix.
Dit autrement, Weever est un site Web à travers duquel il est possible de documenter la progression d'un ou plusieurs projets au travers d'événements temporels liés a un carnet d'adresse et a un système de fichier :
Petit plus, il est possible de partager publiquement tout ou partie d'un projet. Un explorateur de données ouvertes est joint à l'application.
A propos de ce dépôt
Ce dépôt contient le cœur de Weever, toute la logique permettant de démarrer une instance de l'application. Notez qu'elle reste une bibliothèque au sens Javascript, ou dit autrement une dépendance NPM. Il n'est donc pas possible de la démarrer en mode standalone.
Par contre, étant un moteur générique, il est possible de l'inclure dans un nouveau projet pour démarrer une nouvelle instance ad hoc. Cette nouvelle instance pourra :
- être customisée graphiquement avec un thème de couleur à la carte.
- être améliorée dans ses fonctionnalités grâce à un système d'injection de composants.
Une instance de démo existe pour montrer les fonctionnalités de base.
Identifiants de test : jean.demo@gmil.co/test
Manuel pour l'utilisateur
Pour les non-développeurs, le manuel d'utilisateur est disponible sur le wiki de l'instance de Démo.
Manuel pour le développeur
Le plus concret pour savoir comment structurer une instance Weever est de regarder dans le dépôt de Weever de démo.
Dépendances pré-requises
Weever est basé sur une liste de dépendances obligatoires dans sa partie backend et frontend.
Environnement logiciel
Weever est conçu pour interfacer un certain nombre de logiciels tiers.
- Une base de données RDF GraphDB
- Un moteur de recherche ElasticSearch dans lequel GraphDB indexe ses données via un certain nombre de connecteurs.
- Un serveur d'authentification Keycloak
- Un serveur de stockage de fichier en cloud Minio
- Un serveur de téléversement optimisé de fichiers TusD
- Un serveur de génération d'imagette Thumbor
Si cette liste peut paraître imposante, toute cette stack applicative (à part GraphDB) est facile à mettre en place via un fichier docker-compose moyennant le réglage de quelques variables d'environnements abordé dans une section suivante.
Back-End
La partie Back-End de Weever est piloté par la bibliothèque @synaptix/server anciennement nommée synaptix.js
. Cette librairie est une boîte noire interfacée avec un bus sémantique Synaptix et permet de générer une API GraphQL assurant 99% de la gestion de donnée.
Cette API GraphQL permet de :
- Requêter la base de données et l'index de l'application (
Query
au sens GraphQL) - Mettre à jour la base de données. (
Mutation
au sens GraphQL) - Gérer son compte utilisateur (connexion, déconnexion, création de compte, oubli de mot de passe). (
Mutation
au sens GraphQL)
Chaque requête GraphQL est contextualisée par un jeton de session (JWT) et un header HTTP précisant la langue de l'interface.
Le 1% restant est un petit ensemble de middlewares ExpressJS (l'API GraphQL étant un middleware particulier) permettant de renvoyer différentes choses comme des fichiers de langues ou des fichiers normalisés.
La liste de ces middlewares sont les fichiers dans le dossier /src/server/middlewares qui commencent par serve***
La documentation de @synaptix/server
est ici.
Front-End
L'interface graphique de Weever s'appuie sur la librarie @synaptix/ui elle-même basée sur la libraire MUI.
@synaptix/ui
offre des composants clé-en-main et connectés au modèle de données générique Mnémotix.
Note: La documentation de SynaptixUI va venir bientôt...
L'interfaçage de données avec l'API GrapQL s'appuie sur la librairie @synaptix/client (anciennement nommée synaptix-client-toolkit
) elle-même basée sur la librairie @apollo/client
.
D'autres librairie utilisant des contextes React sont déclarées en peerDependencies afin d'éviter des doublons et donc des erreurs. Notamment :
dayjs
pour la gestion de date.formik
pour la gestion des formulaires.react-i18next
pour la gestion des labels i18n.react-router
pour la gestion des routes.
Package.json
En résumé, votre fichier package.json
devra comporter les dépendances suivantes :
{
"@apollo/client": "^3.4.8",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@mnemotix/synaptix-client-toolkit": "^2.5.5",
"@mnemotix/synaptix.js": "^4.12.1",
"@mui/material": "^5.6.4",
"@mui/styles": "^5.6.2",
"@mui/system": "^5.6.4",
"@synaptix/ui": ">=1.0.0",
"dayjs": "^1.10.7",
"formik": "2.2.9",
"graphql": "15.7.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "11.16.2",
"react-router": "5.2.0",
"react-router-dom": "5.3.0"
}
Note: La liste se trouve dans le champ
peerDependencies
du fichier /package.json
Environnement de développement
Weever est une application connectée à un écosystème logiciel (voir section Environnement logiciel
), avant de démarrer une instance de développement, il faut s'assurer d'avoir réglé un certain nombre de variables d'environnements.
Ces variables sont à mettre dans un fichier .env
à la racine du projet.
RDFSTORE_ROOT_URI= # URI du endpoint GraphDB
RDFSTORE_REPOSITORY_NAME= # Nom de la base recevant les données RDF
RDFSTORE_USER= # Identifiant de l'utilisateur GraphDB ayant au RDFSTORE_REPOSITORY_NAME avec accès en R/W
RDFSTORE_PWD= # Mot de passe de l'utilisateur GraphDB
ES_MASTER_URI= # URI du endpoint "master" ES
ES_CLUSTER_USER= # Identifiant de l'utilisateur ES
ES_CLUSTER_PWD= # Mot de passe de l'utilisateur ES
INDEX_VERSION=NATIVE # Laisser NATIVE pour bypasser RabbitMQ et utiliser le client ES natif développé en NodeJS
INDEX_PREFIX_TYPES_WITH= # Préfix [optionel] utilisé pour tous les indices ES
OAUTH_BASE_URL= # URI du endpoint Keycloak
OAUTH_ADMIN_USERNAME= # Identifiant d'un utilisateur ayant des droits d'administrateur
OAUTH_ADMIN_PASSWORD= # Mot de passe d'un utilisateur ayant des droits d'administrateur
OAUTH_REALM= # Identifiant du Realm
OAUTH_REALM_CLIENT_ID=api # Identifiant du client OpenID, laisser "api"
OAUTH_REALM_CLIENT_SECRET= # Mot de passe du client OpenID
Pour prendre en compte ces variables, vous pouvez copier-coller le répertoire /launcher dans votre projet. Ce répertoire contient (entre autre) deux sous répertoires /conf/docker/local et /conf/docker/remote contenant des configuration docker-compose pour démarrer une stack de développement. Le premier (local) démarre une stack entièrement locale, le second démarre une stack se connectant sur un serveur GraphDB, un moteur ES, et un SSO keycloak distants.
L'éxécutable /launcher/console.sh permet dans lancer les deux stacks.
REMOTE_MODE=(1|0) ./launcher/console.sh start
Notez qu'il faut lancer cette commande à la première installation
./launcher/console.sh install
Installation de données préliminaires
Un certain nombre de choses ne sont pas encore automatisées et nécéssite une intervention manuelle.
Créer une nouvelle base GraphDB et charger les ontologies
Dans le Workbench GraphDB, créer un nouveau "repository" qui prendra le même nom que la variable d'environnement RDFSTORE_REPOSITORY_NAME
ainsi que qu'un utilisateur associé en Read/Write dont les identifiants auront les mêmes valeurs que les variables RDFSTORE_USER
et RDFSTORE_PWD
.
Attention, dans le formulaire de création de répo, ne pas oublier de sélectionner le "Ruleset Mnémotix" rdfs-plus.mnx.pie
Une fois le répo créé, charger les ontologies disponibles dans le dossier /documentation/data/graphdb.
Ontologies standardisées :
- foaf.ttl
- geonames.ttl
- prov.ttl
- sioc.ttl
- skos.ttl
Ontolologies Mnémotix :
- mnx.ttl
- mnx-project.ttl
- mnx-skos.ttl
Instantier les connecteurs ES
Lancer la commande :
yarn data-index -a
Elle aura pour effet de créer un connecteur GraphDB -> ES par type RDF utilisé par Weever. Un connecteur verse ses données dans un index ES préfixé pa valeur de la variable d'environnement INDEX_PREFIX_TYPES_WITH
Créer un Realm Keycloak
Compléter le template disponible /documentation/templates/realm.json et charger le dans l'interface de création de Realm Keycloak.
Mettre sur pied une instance Weever
Weever Core expose un certain nombre d'éléments pour démarrer une instance Weever rapidement.
Préparation
Weever Core expose l'utilitaire /src/server/launch.js pour lancer le backend de Weever.
L'exemple de lancement le plus simple possible se trouve dans le fichier entrypoint de l'instance Weever Démo.
import { launch } from "@mnemotix/weever-core/server";
import Package from "../../package.json";
import environmentDefinition from "./config/environment";
import webpackConfig from "../../webpack.config";
launch({
Package,
environmentDefinition,
locales: {},
webpackConfig
});
Package
fait référence au fichierpackage.json
importé dans un objet JSenvironmentDefinition
est un fichier surchargeant (et ajoutant) la définition des variables d'environnement par défaut et disponibles dans le fichier /src/server/config/environment.js (la description des variables est définie dans le fichier).
Note: Il est possible de surcharger les variables directement dans le fichier
.env
.
locales
est un objet JS au format i18n permettant de surcharger les fichiers de langues par défaut et disponibles dans le fichier /src/locales/index.js
Note: Le premier niveau de clés cet objet est la liste des langues disponible. Pour l'instant
fr|en
.
webpackConfig
est un objet JS au format Webpack explicité ci-dessous. Webpack est le bundler retenu pour générer (et optimiser) les fichiers JS/CSS/Assets à envoyer au navigateur Web pour démarrer le Front-End de Weever
L'exemple le plus minimal est celui-ci :
const path = require("path");
const { generateWebpackConfig } = require("@mnemotix/weever-core/server");
module.exports = generateWebpackConfig({
distPath: path.resolve(__dirname, "dist"),
entries: [
{
name: "weever",
entry: path.resolve(__dirname, "./src/client/launchWeever.js"),
html: {
template: path.resolve(__dirname, "./src/client/index.tpl.html"),
filename: "index.html",
},
}
],
});
Weever Core expose la méthode utilitaire generateWebpackConfig qui attend le paramètre distPath
(répertoire dans lequel générer les fichiers compilés et assemblés) et entries
(les points d'entrées du code Front-End JS).
L'exemple ci-dessus va générer dans le répertoire /dist
les assets (JS/CSS/Images/Font/autres) compilés et assemblés à partir du point d'entrée JS ./src/client/launchWeever.js
injecté dans le template HTML ./src/client/index.tpl.html
.
Le template HTML minimal ./src/client/index.tpl.html
à créer est celui-ci :
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
</head>
<body>
<div id="react-root"/>
</body>
</html>
La seule balise importante à ne pas omettre est <div id="react-root"/>
. C'est la balise d'injection du code ReactJS définissant le code HTML Front-End de l'instance Weever.
Quant au point d'entrée JS de Weever ./src/client/launchWeever.js
à définir, le code minimal est celui-ci :
import {launchWeever} from "@mnemotix/weever-core/launchWeever";
import {theme} from "./theme";
import possibleTypes from "./gql/possibleTypes.json";
launchWeever({
possibleTypes,
theme,
extensions: []
});
possibleTypes
est un object JS nécéssaire au client GraphQL Apollo pour résoudre les fragments GQL. Ce fichier doit être généré à chaque changement de définition du schéma GraphQL. Pour cela deux options :- Si votre modèle ne spécifie pas celui de base. Copier-coller le fichier /documentation/possibleTypes.json dans
./src/client/gql/possibleTypes.json
fera l'affaire. - Sinon, appellez la commande
yarn gql-fragments [chemin de votre définition de modèle]
qui le fera pour vous.
- Si votre modèle ne spécifie pas celui de base. Copier-coller le fichier /documentation/possibleTypes.json dans
theme
est un objet JS définissant le thème graphique de l'application au format MUI.extensions
est la liste des extensions pour surcharger le code FrontEnd de l'application décrit dans Weeever Core. Voir la sectionExtensions
dédiée.
Lancement
Une fois la préparation terminée. Deux méthodes de lancement.
- Utiliser l'utilitaire @babel/node
babel-node ./src/server/entrypoint.js
- Utiliser la librairie JS @babel/register et créer un fichier de transpilation intermédiaire à la racine du projet
entrypoint.js
require("@babel/register");
require("./src/server/entrypoint");
Puis lancer la commande :
node ./entrypoint.js
Note : Tous les fichiers décrits dans la section
Lancement
etPréparation
sont disponibles dans le dépôt de l'instance Weever de démo avec plus d'options et de détails (notamment l'intégration de Koncept dans les points d'entrée du fichier de configuration de Webpack)
Étendre Weever
Extensions
Comme évoqué dans la section précédente l'utilitaire @mnemotix/weever-core/launchWeever peut attendre le paramètre extensions
.
C'est une liste d'instance d'objet Extension.
Un exemple étant le plus parlant, on peut reprendre l'exemple ci-dessus en ajoutant une extension :
import {ROUTES} from "@mnemotix/weever-core";
import {Extension} from "@synaptix/ui";
import {Route} from "react-router-dom";
import loadable from "@loadable/component";
import {launchWeever} from "@mnemotix/weever-core/launchWeever";
import {theme} from "./theme";
import possibleTypes from "./gql/possibleTypes.json";
// Routes are code splitted to decrease the size of initial laaded JS bundle file
const InjectedFragment = loadable(() => import("./components/InjectedFragment"));
const OverridedComponent = loadable(() => import("./components/OverridedComponent"));
const AddedComponent = loadable(() => import("./components/AddedComponent"));
const extension = new Extension({
name: "MyCoolExtension",
generateTopRoutes: ({isContributor, isAdmin, isEditor} = {}) => (
<>
<Route exact path={ROUTES["route_to_override"]} component={OverridedComponent} />
<If condition={isContributor}>
<Route exact path={"/new_route"} component={AddedComponent} />
</If>
</>
),
fragments: {
"Project.Heading.AfterTags": InjectedComponent,
},
settings: {
"ProjectsListVisibleColumnNames": ["image", "title", "tags", "status", "updatedAt", "accessPolicy"]
}
});
launchWeever({
possibleTypes,
theme,
extensions: [extension]
});
Dans cet exemple, trois types d'extensions sont utilisées.
Surchage de haut niveau
La surchage complète et/ou ajout d'une route de haut niveau se fait dans le paramètre generateTopRoutes
qui prend une fonction et attend une liste de routes à ajouter au début des routes déjà existantes. React Router s'arrêtant sur la première route matchant l'URL demandée, si deux routes avec le même chemin sont définies, la première l'emporte.
Surchage d'une portion de composant
La Surchage d'une portion de composant se fait dans le paramètre fragments
qui est un object de clés/valeurs. Les clés étant les identifiants des points d'injection dans l'application et les valeurs, le composant React à monter.
Dans le code de Weever Core vous pouvez trouver des choses comme dans src/client/components/routes/Project/Project.js :
useExtensionFragment("Project.Heading.AfterTags", {projectId: "..."});
Il s'agit d'un point d'injection possible défini par l'identifiant Project.Heading.AfterTags
.
Dans l'exemple ci-dessus, le composant InjectedComponent
est monté sous la forme <InjectedComponent projectId="..."/>
au niveau de ce point d'injection.
Le hook correspondant pour récupérer ce composant est exposé dans @synaptix/ui et s'appelle useExtensionFragment :
Surchage d'une configuration particulière
D'autres portions de l'application sont pilotées grâce à des configurations simples. Dans l'exemple plus haut la clé ProjectsListVisibleColumnNames
concerne les colonnes visibles par défaut de la liste des projets.
Le hook correspondant pour récupérer cette valeur est exposé dans @synaptix/ui et s'appelle useExtensionSettings :
useExtensionSettings("ProjectsListVisibleColumnNames")
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago