This commit is contained in:
Alexis Métaireau 2023-11-23 13:48:45 +01:00
parent 64f64889d1
commit cd8fabc3d0
11 changed files with 610 additions and 52 deletions

View file

@ -1,8 +1,7 @@
---
title: Adding Real-Time Collaboration to uMap, first week
headline: A heads-up on what I've been doing this week on uMap
tags: Python, CRDT, Sync
status: draft
tags: Python, CRDT, Sync, uMap
---
Last week, I've been lucky to start working on [uMap](https://github.com/umap-project/umap/), an open-source map-making tool to create and share customizable maps, based on Open Street Map data.

View file

@ -1,5 +1,5 @@
---
title: Importing a PostgreSQL dump under a different table name
title: Importing a PostgreSQL dump under a different database name
headline: Simple commands to help you during an import
tags: postgresql, sysadmin
---
@ -18,7 +18,7 @@ gpg --decrypt hb_chariotte_prod.pgdump.asc > hb_chariotte_prod.pgdump
scp hb_chariotte_prod.pgdump chariotte:.
```
## Importing while changing ACLs and table name
## Importing while changing ACLs and database name
On the server, here is the command to change the name of the database and the user. The file I received was using the so-called "custom" format, which is not editable with a simple editor, so you have to export it to SQL first, and then edit it before running the actual queries.

View file

@ -0,0 +1,118 @@
---
title: Adding Real-Time Collaboration to uMap, second week
headline: A heads-up on what I've been doing this week on uMap
tags: Python, CRDT, Sync, uMap
---
I continued working on [uMap](https://github.com/umap-project/umap/), an open-source map-making tool to create and share customizable maps, based on Open Street Map data.
Here is a summary of what I did:
- I reviewed, rebased and made some minor changes to [a pull request which makes it possible to merge geojson features together](https://github.com/umap-project/umap/pull/772) ;
- I've explored around the idea of using SQLite inside the browser, for two reasons : it could make it possible to use the [Spatialite](https://www.gaia-gis.it/fossil/libspatialite/index) extension, and it might help us to implement a CRDT with [cr-sqlite](https://github.com/vlcn-io/cr-sqlite) ;
- I learned a lot about the SIG field. This is a wide ecosystem with lots of moving parts, which I understand a bit better now.
## The optimistic-merge approach
There were an open pull request implementing an "optimistic merge". We spent some time together with Yohan to understand what the pull request is doing, discuss it and made a few changes.
Here's the logic of the changes:
1. On the server-side, we detect if we have a conflict between the incoming changes and what's stored on the server (is the last document save fresher than the `IF-UNMODIFIED-SINCE` header we get ?) ;
2. In case of conflict, find back the reference document in the history (let's name this the "local reference") ;
3. Merge the 3 documents together, that is :
1. Find what the the incoming changes are, by comparing the incoming doc to the local reference.
2. Re-apply the changes on top of the latest doc.
One could compare this logic to what happens when you do a `git rebase`. Here is some pseudo-code:
```python
def merge_features(reference: list, latest: list, incoming: list):
"""Finds the changes between reference and incoming, and reapplies them on top of latest."""
if latest == incoming:
return latest
reference_removed, incoming_added = get_difference(reference, incoming)
# Ensure that items changed in the reference weren't also changed in the latest.
for removed in reference_removed:
if removed not in latest:
raise ConflictError
merged = copy(latest)
# Reapply the changes on top of the latest.
for removed in reference_removed:
merged.delete(removed)
for added in incoming_added:
merged.append(added)
return merged
```
The pull request is not ready yet, as I still want to add tests with real data, and enhance the naming, but that's a step in the right direction :-)
## Using SQLite in the browser
At the moment, (almost) everything is stored on the server side as GeoJSON files. They are simple to use, to read and to write, and having them on the storage makes it easy to handle multiple revisions.
I've been asked to challenge this idea for a moment. What if we were using some other technology to store the data? What would that give us? What would be the challenges?
I went with SQLite, just to see what this would mean.
- SQLite is originally not made to work on a web browser, but thanks to Web Assembly, it's possible to use it. It's not **that** big, but the library weights 2Mb.
- With projects such as [CR-SQLite](https://github.com/vlcn-io/cr-sqlite), you get a way to add CRDTs on top of SQLite databases. Meaning that the clients could send their changes to other clients or to the server, and that it would be easy to integrate ;
- The clients could retrieve just some part of the data to the server (e.g. by specifying a bounding box), which gives it the possibility to not load everything in memory if that's not needed.
I wanted to see how it would work, and what would be the challenges around this technology. I wrote a small application with it. Turns out writing to a local in-browser SQLite works.
Here is what it would look like:
- Each client will get a copy of the database, alongside a version ;
- When clients send changes, you can just send the data since the last version ;
- Databases can be merged without loosing data, the operations done in SQL will trigger writes to a specific table, which will be used as a CRDT.
I'm not sure SQLite by itself is useful here. It sure is fun, but I don't see what we get in comparison with a more classical CRDT approach, except complexity. The technology is still quite young and rough to the edges, and uses Rust and WebASM, which are still strange beasts to me.
## Related projects in the SIG field
Here are some interesting projects I've found this week :
- [Leaflet.offline](https://allartk.github.io/leaflet.offline/) allows to store the tiles offline ;
- [geojson-vt](https://github.com/mapbox/geojson-vt) uses the concept of "vector tiles" I didn't know about. Tiles can return binary or vectorial data, which can be useful to just get the data in one specific bounding box This allows us for instance to store GeoJSON in vector tiles.
- [mapbox-gl-js](https://github.com/mapbox/mapbox-gl-js) makes it possible to render SIG-related data using WebGL (no connection with Leaflet)
- [leaflet-ugeojson](https://github.com/BenjaminVadant/leaflet-ugeojson) and [leaflet.Sync](https://github.com/jieter/Leaflet.Sync) allows multiple people to share the same view on a map.
Two libraries seems useful for us:
- [Leaflet-GeoSSE](https://github.com/ATran31/Leaflet-GeoSSE) makes it possible to use SSE (Server Sent Events) to update local data. It uses events (create, update, delete) and keys in the GeoJSON features..
- [Leaflet Realtime](https://github.com/perliedman/leaflet-realtime) does something a bit similar, but doesn't take care of the transport. It's meant to be used to track remote elements (a GPS tracker for instance)
I'm noting that:
- In the two libraries, unique identifiers are added to the `features` to allow for updates.
- None of these libraries makes it possible to track local changes. That's what's left to find.
## How to transport the data?
One of the related subjects is transportation of the data between the client and the server. When we'll get the local changes, we'll need to find a way to send this data to the other clients, and ultimately to the server.
There are multiple ways to do this, and I spent some time trying to figure out the pros and cons of each approach. Here is a list:
- **WebRTC, the P2P approach**. You let the clients talk to each other. I'm not sure where the server fits in this scenario. I've yet to figure-out how this works out in real-case scenarii, where you're working behind a NAT, for instance. Also, what's the requirement on STUN / Turn servers, etc.
- Using **WebSockets** seems nice at the first glance, but I'm concerned about the resources this could take on the server. The requirement we have on "real-time" is not that big (e.g. if it's not immediate, it's okay).
- Using **Server Sent Events** is another way to solve this, it seems lighter on the client and on the server. The server still needs to keep connexion opens, but I've found some proxies which will do that for you, so it would be something to put between the uMap server and the HTTP server.
- **Polling** means less connexion open, but also that the server will need to keep track of the messages the clients have to get. It's easily solvable with a Redis queue for instance.
All of these scenarii are possible, and each of them has pros and cons. I'll be working on a document this week to better understand what's hidden behind each of these, so we can ultimately make a choice.
### Server-Sent Events (SSE)
Here are some notes about SSE. I've learned that:
- SSE makes it so that server connections never ends (so it consumes a process?)
- There is a library in Django for this, named [django-eventstream](https://github.com/fanout/django-eventstream)
- [Django channels](https://channels.readthedocs.io/en/latest/) aims at using ASGI for certain parts of the app.
- You don't have to handle all this in Django. It's possible to delegate it to [pushpin](https://github.com/fastly/pushpin), a proxy, using [django-grip](https://github.com/fanout/django-grip)
It's questioning me in terms of infrastructure changes.

View file

@ -0,0 +1,241 @@
---
title: Using pelican to track my worked and volunteer hours
headline: Graphs, progress-bars and python-markdown extensions
tags: Pelican, Work, Vega, Markdown
---
I was tracking my hours in Datasette ([article](https://blog.notmyidea.org/using-datasette-for-tracking-my-professional-activity.html) and [follow-up](https://blog.notmyidea.org/deploying-and-customizing-datasette.html)), but I wasn't really happy with the editing process.
I've seen [David](https://larlet.fr/david) notes, which made me want to do something similar.
I'm consigning everything in markdown files and as such, was already keeping track of everything this way already. Tracking my hours should be simple otherwise I might just oversee it. So I hacked something together with [pelican](https://github.com/getpelican/pelican) (the software I wrote for this blog).
![A graph showing the worked hours and volunteer hours](/images/pelican/worklog.png)
It's doing the following:
1. Defines a specific format for my worklog entries
2. Parses them (using a regexp), does some computation and ;
3. Uses a specific template to display a graph and progress bar.
## Reading information from the titles
I actually took the format I've been already using in my log, and enhanced it a bit.
Basically, the files look likes this (I'm writing in french):
```markdown
---
title: My project
total_days: 25
---
## Mardi 23 Novembre 2023 (9h, 5/5)
What I did this day.
I can include [links](https://domain.tld) and whatever I want.
It won't be processed.
## Lundi 22 Novembre 2023 (8h rémunérées, 2h bénévoles, 4/5)
Something else.
```
Basically, the second titles (h2) are parsed, and should have the following structure:
`{day_of_week} {day} {month} {year} ({worked_hours}(, optional {volunteer_hours}), {fun_rank})`
The goal here is to retrieve all of this, so I asked ChatGPT for a regexp and iterated on the result which got me:
```python
pattern = re.compile(
r"""
(\w+)\s+ # Day name
(\d{1,2})\s+ # Day number
([\wéû]+)\s+ # Month name
(\d{4})\s+ # Year
\(
(\d{1,2})h # Hours (mandatory)
(?:\s+facturées)? # Optionally 'facturées', if not present, assume hours are 'facturées'
(?:,\s*(\d{1,2})h\s*bénévoles)? # Optionally 'volunteer hours 'bénévoles'
,? # An optional comma
\s* # Optional whitespace
(?:fun\s+)? # Optionally 'fun' (text) followed by whitespace
(\d)/5 # Happiness rating (mandatory, always present)
\) # Closing parenthesis
""",
re.VERBOSE | re.UNICODE,
)
```
## The markdown preprocessor
I'm already using a custom pelican plugin, which makes it possible to have pelican behave exactly the way I want. For instance, it's getting the date from the filesystem.
I just had to add some features to it. The way I'm doing this is by [using a custom Markdown reader](https://docs.getpelican.com/en/3.6.2/plugins.html#how-to-create-a-new-reader), on which I add extensions and custom processors.
In my case, I added a preprocessor which will only run when we are handling the worklog. It makes it possible to change what's being read, before the markdown lib actually transforms it to HTML.
Here is the code for it:
```python
class WorklogPreprocessor(Preprocessor):
pattern = "the regexp we've seen earlier"
def run(self, lines):
new_lines = []
for line in lines:
if line.startswith("##"):
match = re.search(self.pattern, line)
if not match:
raise ValueError("Unable to parse worklog title", line)
(
day_of_week,
day,
month,
year,
payed_hours,
volunteer_hours,
happiness,
) = match.groups()
volunteer_hours = int(volunteer_hours) if volunteer_hours else 0
payed_hours = int(payed_hours)
happiness = int(happiness)
date = datetime.strptime(f"{day} {month} {year}", "%d %B %Y")
self.data[date.strftime("%Y-%m-%d")] = {
"payed_hours": payed_hours,
"volunteer_hours": volunteer_hours,
"happyness": happiness,
}
# Replace the line with just the date
new_lines.append(f"## 🗓️ {day_of_week} {day} {month} {year}")
else:
new_lines.append(line)
return new_lines
```
It does the following when it encounters a h2 line:
- try to parse it
- store the data locally
- replace the line with a simpler version
- If if doesn't work, error out.
I've also added some computations on top of it, which makes it possible to display a percentage of completion for the project, if "payed_hours" was present in the metadata, and makes it use a specific template (see later).
```python
def compute_data(self, metadata):
done_hours = sum([item["payed_hours"] for item in self.data.values()])
data = dict(
data=self.data,
done_hours=done_hours,
template="worklog",
)
if "total_days" in metadata:
total_hours = int(metadata["total_days"]) * 7
data.update(
dict(
total_hours=total_hours,
percentage=round(done_hours / total_hours * 100),
)
)
return data
```
## Plugging this with pelican
Here's the code for extending a custom reader, basically adding a pre-processor and adding back its data in the document metadata:
```python
is_worklog = Path(source_path).parent.match("pages/worklog")
if is_worklog:
worklog = WorklogPreprocessor(self._md)
self._md.preprocessors.register(worklog, "worklog", 20)
# process the markdown, and then
if is_worklog:
metadata["worklog"] = worklog.compute_data(metadata)
```
## Adding a graph
Okay, everything is parsed, but it's not yet displayed on the pages. I'm using [vega-lite](https://vega.github.io/vega-lite/docs/) to display a graph.
Here is my template for this (stored in `template/worklog.html`), it's doing a stacked bar chart with my data.
```js
const spec = {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": 500,
"height": 200,
"data":
{
"name": "table",
"values": [
{% for date, item in page.metadata.worklog.data.items() %}
{"date": "{{ date }}", "series": "Rémunéré", "count": {{ item['payed_hours'] }}},
{"date": "{{ date }}", "series": "Bénévole", "count": {{ item['volunteer_hours'] }}},
{% endfor %}
]
}
,
"mark": "bar",
"encoding": {
"x": {
"timeUnit": {"unit": "dayofyear", "step": 1},
"field": "date",
"axis": {"format": "%d/%m"},
"title": "Date",
"step": 1,
},
"y": {
"aggregate": "sum",
"field": "count",
"title": "Heures",
},
"color": {
"field": "series",
"scale": {
"domain": ["Bénévole", "Rémunéré"],
"range": ["#e7ba52", "#1f77b4"]
},
"title": "Type d'heures"
}
}
};
vegaEmbed("#vis", spec)
// result.view provides access to the Vega View API
.then(result => console.log(result))
.catch(console.warn);
```
I've also added a small progress bar, made with unicode, which looks like this.
```
▓▓░░░░░░░░ 29% (51h / 175 prévues)
```
Here is the code for it:
```jinja2
{% if "total_days" in page.metadata.keys() %}
{% set percentage = page.metadata.worklog['percentage'] %}
{% set total_blocks = 10 %}
{% set percentage_value = (percentage / 100.0) %}
{% set full_blocks = ((percentage_value * total_blocks) | round(0, 'floor') ) | int %}
{% set empty_blocks = total_blocks - full_blocks %}
<div class="progressbar">
{# Display full blocks #}
{% for i in range(full_blocks) %}▓{% endfor %}
{# Display empty blocks #}
{% for i in range(empty_blocks) %}░{% endfor %}
{{ percentage }}% ({{ page.metadata.worklog['done_hours'] }}h / {{ page.metadata.worklog['total_hours'] }} prévues)
</div>
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -0,0 +1,102 @@
---
title: Argos
save_as: argos/index.html
template: worklog
total_days: 8
---
## Mardi 23 Novembre 2023 (0h, 1h bénévoles, 5/5)
J'ai passé un peu de temps à répondre a des tickets, merger et faire des petites modifications dans le code, c'était très plaisant.
Il est maintenant [possible de lancer le serveur depuis l'extérieur du venv](https://framagit.org/framasoft/framaspace/argos/-/merge_requests/8), et j'ai lu quelques demandes de modifications de Luc.
## Jeudi 16 Novembre 2023 (0h, 1h bénévoles, 5/5)
- [Migré vers la nouvelle manière de définir la config dans Pydantic](https://framagit.org/framasoft/framaspace/argos/-/merge_requests/6)
## Lundi 06 Novembre 2023 (0h, 2h bénévoles, 5/5)
J'ai passé un peu de temps avec Matthieu Leplatre pour faire une revue du code que j'ai écrit.
## Jeudi 19 Octobre 2023 (6h, 2h bénévoles, 5/5)
Préparation de la discussion avec Luc, puis j'ai ajouté de la documentation et j'ai fait pas mal de finitions.
## Mercredi 18 Octobre 2023 (7h, 1h bénévoles, 5/5)
J'ai passé une journée de travail dessus.
Beaucoup de choses faites. La base de code est plus solide, et surtout testée ce qui me rassure.
## Mardi 17 Octobre 2023 (1h, 4/5)
- J'ai ajouté les dépendences dans le pyproject.toml
- Updated the CLI interface
## Mercredi 11 Octobre 2023 (7h, 1h bénévoles, 4/5)
J'ai passé la journée dessus.Il me reste 3 demi journées.
J'ai passé beaucoup de temps à mettre en place des tests au niveau de l'app. La manière dont l'application était initialisée ne permettait pas de l'utiliser dans les tests. Je n'ai pas tout à fait terminé, mais je pense que c'est le moment pour essayer de passer à PostgreSQL, parce que c'est ce qui va tourner en production.
De ce que je comprends, plusieurs approches :
1. Utiliser une connection pool
2. Faire des requêtes en asynchrone (voir [la doc](https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html))
Je suis vraiment pas loin d'un truc bien, j'aimerai quand même vraiment avoir des tests pour tester les cas limites, là beaucoup de choses se font à vue et j'ai peur que des bugs ne se cachent.
Fait :
- [x] Ajouter une notion de sévérité dans la config
- [x] Ajouter une notion de fréquence dans la config
## Mardi 10 Octobre 2023 (5h, 4/5)
- [x] Sécuriser les appels des agents au backend
- [x] Ajouter le support pour les !include
## Lundi 09 Octobre 2023 (7h, 4/5)
- [x] Terminer le backend de vérification SSL
- [x] Décider comment faire pour faire remonter les différents niveaux d'alerte (L'agent ne devrait pas décider, c'est le travail du serveur)
J'ai pas mal refactoré le code, et c'est maintenant possible de faire une partie des checks (la finalisation) côté serveur. Nous avons aussi maintenant un check SSL fonctionnel.
La base de données gère maintenant une relation, et j'ai commencé à écrire quelques tests également, ainsi que packagé ça dans un package python.
C'était plutôt une journée un peu décousue, mais je suis content du résultat. C'était beaucoup de grosses modifications et donc avoir le temps de le faire pour avoir le contexte en tête aide pas mal.
## Jeudi 05 Octobre 2023 (2h, 4/5)
Je n'ai pas pris de notes.
## Mercredi 04 Octobre 2023 (4h, 4/5)
J'ai pu avancer, mais je me suis confronté à plusieurs soucis de concentration. Ici au coworking c'est aussi un endroit pour socialiser, et donc je suis moins efficace. Les pomodoros m'aident à me recentrer, j'ai quasiment une API fonctionnelle. Je galère un peu avec Pydantic parce que je ne le connais pas encore bien, j'espère que ce sont des efforts qui vont payer sur le long terme.
La prochaine fois je pense avancer sur les requêtes pour ordonnancer tout ça, et faire quelques tests pour valider que tout marche bien comme je veux.
## Mardi 03 Octobre 2023 (3h, 4/5)
Session de travail assez agréable. J'ai commencé à faire fonctionner l'outil, et je commence maintenant à travailler sur les checks en tant que tels. Je sens qu'il t a quelques fragilités au niveau de la structure des données (schémas / validation avec Pydantic).
Je pense que la prochaine étape sera de faire tous les checks, et de les faire retourner des données à l'API. Puis ensuite de s'assurer que les jobs ne sont pas distribués à plusieurs workers en paralelle.
## Lundi 02 Octobre 2023 (4h, 4/5)
Je suis content de cette session de travail. J'ai repensé encore une fois le modèle de données pour arriver à quelque chose qui me semble mieux. J'ai continué à travailler sur la codebase avec fastapi que je trouve plus élégante et sur laquelle il sera possible de passer en asynchrone ensuite.
Les fichiers de configuration, une fois parsés, permettent de créer les enregistrements dans la base de données. La première version de l'API est bientôt prête. La prochaine étape est de bosser sur les workers.
## Mercredi 27 Septembre 2023 (4h, 4/5)
1h pour mettre en place un bouncer ZNC. J'ai plutôt fait des recherches sur des outils alternatifs pour l'API, suite à mon impression de passer du temps à refaire des choses qui sont déjà fournies par d'autres outils. J'espère que c'était une bonne idée ! Le prix à payer pour me remettre le pied à l'étrier. En fin de journée j'ai réussi à vraiment réfléchir au problème métier, et à déterminer un bon modèle de données ainsi que des scénarios d'utilisation. Au final, je pense qu'il faut passer par fastapi (qui propose de l'asynchrone ASGI de base) mais rester sur SQLAlchemy (Pewee à une API qui semble plus simple, mais qui ne supporte pas très bien l'asynchronicité). Pour la suite, je pense qu'il faut que je me concentre plus sur les fonctionalités de base.
## Mardi 26 Septembre 2023 (4h, 4/5)
J'ai continué de bootstrapper et j'ai importé des bouts de codes qui manquaient pour lancer le serveur web, gérer la configuration du service, la gestion de la base de données, des migrations etc.
## Lundi 25 Septembre 2023 (3h, 4/5)
J'ai commencé à boostraper le projet, fait un module qui est capable de lire le fichier de configuration (en YAML) et de valider que ce qui s'y trouve est correct. J'utilise Pydantic pour ça, que je ne connaissais pas.

View file

@ -0,0 +1,9 @@
---
title: Notmyidea
save_as: notmyidea/index.html
template: worklog
---
## Mercredi 23 Novembre 2023 (0h, 5h bénévoles, 5/5)
J'ai passé du temps à coder un système qui me permet de faire le suivi de mes heures, par projet.

View file

@ -0,0 +1,76 @@
---
title: uMap
save_as: umap/index.html
template: worklog
total_days: 25
---
## Mardi 23 Novembre 2023 (9h, 5/5)
J'ai exploré l'utilisation de Websockets pour le transport, entre autre sa consommation mémoire, il semblerait que ce soit tout à fait acceptable (1gb de mémoire permet de gérer 1500 connexions concurrentes).
WebRTC n'est [actuellement pas supporté par Tor Browser ](https://gitlab.torproject.org/legacy/trac/-/issues/8178)([pour le moment](https://gitlab.torproject.org/tpo/applications/tor-browser/-/issues/41021)), donc j'imagine que c'est une fausse piste.
J'ai repassé un bon coup sur la PR du merge optimiste. Je suis content du résultat: le code et des tests me semblent plus lisibles et compréhensibles.
L'après-midi à été passée avec Yohan qui m'a fait un tour du frontend. J'en ai profité pour prendre des notes que je pense publier dans la documentation. C'était très utile d'avoir ses explications, le code n'est pas si simple à prendre en main.
## Lundi 22 Novembre 2023 (8h, 4/5)
Une matinée passée à la fois à préparer la semaine et à rédiger un résumé de ce que j'ai fait la semaine dernière.
J'ai passé un peu plus de temps à comprendre en profondeur le code de merge de la PR de Biondi biondo, pour pouvoir l'expliquer dans un article de blog.
L'après-midi j'ai participé à la weekly et lu l'article de blog de Figma qui explique leur approche pour implementer du temps réel.
J'avance petit à petite sur la piste d'utiliser un CRDT "maison", voire pas de CRDT du tout, en fonction de nos besoins réels. Un CRDT nous permettrait d'avoir plusieurs personnes qui travaillent en même temps sur une même feature (au sens GeoJSON), mais je ne sais pas encore si c'est un cas d'usage réel.
## Samedi 18 Novembre 2023 (0h, 4h bénévoles, 3/5)
J'ai passé un peu de temps à intégrer l'intégration continue de Github. Je pensais que ce serait rapide mais je ne devais pas être très réveillé…
## Vendredi 17 Novembre 2023 (6h, 3/5)
J'ai passé du temps pour essayer de comprendre comment utiliser SQLite en local à l'intérieur d'un navigateur, en utilisant [cr-sqlite](https://vlcn.io/docs/cr-sqlite/intro). J'ai un prototype qui fonctionne à peu près et qui permet de récupérer les éditions en local pour les synchroniser avec une autre base SQLite.
Fait un point avec l'équipe sur l'avancement général l'après-midi.
Ensuite continué à creuser sur l'utilisation de SQLite avec cr-sqlite.
## Mardi 14 Novembre 2023 (8h, 2/5)
Une matinée passée avec Yohan pour à la fois [avancer sur la PR pour merger des conflits simples](https://github.com/umap-project/umap/pull/772/). On a passé le code en revue et fait quelques changements cosmétiques qui devraient aider à la compréhension générale.
La deuxième partie de la matinée à été utilisée pour discuter des découvertes et des questions que je me pose quand à comment faire pour ajouter ces fonctions de collaboration temps réel.
Plusieurs trucs à noter :
- Il est possible de challenger l'utilisation de geoJSON pour le stockage des données. On a parlé entre autres de pmtiles et de sqlite.
J'ai passé un début d'après-midi à installer mon environnement de travail sur Linux, puis j'ai :
- terminé de rebaser la pull request pour faire un merge optimiste.
- amélioré la vitesse d'execution des tests
Découvertes :
- https://www.geopackage.org/
- https://vlcn.io/docs/js/reactivity
## Lundi 13 Novembre 2023 (8h, 4/5)
J'ai cherché à comprendre comment il serait possible de s'intégrer avec Leaflet. Je connais assez mal l'écosystème donc j'ai cherché les plugins autour de stockage de données et de la synchronisation.
Beaucoup de clicks, de lecture et de compréhension des contours de l'écosystème SIG, et de l'écosystème de Leaflet.
J'ai aussi creusé autour des SSE et de WebRTC, question de comprendre les limites et avantages de chacun.
## Mardi 07 Novembre 2023 (8h, 3/5)
- Lu la documentation d'automerge
- Commencé à faire un prototype pour voir le fonctionnement d'automerge en python
- Installé les dépendances rust, compilé automerge
- Réunion discussion avec Yohan sur mes questions et sur les différentes pistes
## Lundi 06 Novembre 2023 (4h, 4/5)
- Lu le code qui est dans uMap actuellement pour comprendre le fonctionnement actuel
- Commencé à rédiger un document avec les différentes options pour faire de la synchro
- Fais des recherches sur les différentes options pour faire de la synchro

View file

@ -476,7 +476,7 @@ dd {
}
.progressbar {
font-size: 0.5em;
font-size: 1em;
display: inline;
float: right;
float: left;
}

View file

@ -6,8 +6,8 @@
{% endblock extra_head %}
{% block content %}
<header>
<h1 class="post-title">
{{ page.title }}
<h1 class="post-title">{{ page.title }}</h1>
{% if "total_days" in page.metadata.keys() %}
{% set percentage = page.metadata.worklog['percentage'] %}
{% set total_blocks = 10 %}
{% set percentage_value = (percentage / 100.0) %}
@ -18,9 +18,9 @@
{% for i in range(full_blocks) %}▓{% endfor %}
{# Display empty blocks #}
{% for i in range(empty_blocks) %}░{% endfor %}
{{ percentage }}%
{{ percentage }}% ({{ page.metadata.worklog['done_hours'] }}h / {{ page.metadata.worklog['total_hours'] }} prévues)
</div>
</h1>
{% endif %}
</header>
<article>
<div id="vis"></div>
@ -37,7 +37,7 @@
"values": [
{% for date, item in page.metadata.worklog.data.items() %}
{"date": "{{ date }}", "series": "Rémunéré", "count": {{ item['payed_hours'] }}},
{"date": "{{ date }}", "series": "Bénévole", "count": {{ item['volonteer_hours'] }}},
{"date": "{{ date }}", "series": "Bénévole", "count": {{ item['volunteer_hours'] }}},
{% endfor %}
]
}

View file

@ -1,12 +1,13 @@
import os.path
from datetime import datetime
import locale
import os.path
import re
from datetime import datetime
from pathlib import Path
from markdown.preprocessors import Preprocessor
from pelican import signals
from pelican.readers import Markdown, MarkdownReader, pelican_open
from pelican.utils import get_date, slugify
import re
locale.setlocale(locale.LC_TIME, "fr_FR")
@ -24,7 +25,7 @@ class WorklogPreprocessor(Preprocessor):
(?:,\s*(\d{1,2})h\s*bénévoles)? # Optionally 'volunteer hours 'bénévoles'
,? # An optional comma
\s* # Optional whitespace
(?:plaisir\s+)? # Optionally 'plaisir' (text) followed by whitespace
(?:fun\s+)? # Optionally 'fun' (text) followed by whitespace
(\d)/5 # Happiness rating (mandatory, always present)
\) # Closing parenthesis
""",
@ -40,26 +41,26 @@ class WorklogPreprocessor(Preprocessor):
for line in lines:
if line.startswith("##"):
match = re.search(self.pattern, line)
print(match)
if match:
if not match:
raise ValueError("Unable to parse worklog title", line)
(
day_of_week,
day,
month,
year,
payed_hours,
volonteer_hours,
volunteer_hours,
happiness,
) = match.groups()
volonteer_hours = int(volonteer_hours) if volonteer_hours else 0
volunteer_hours = int(volunteer_hours) if volunteer_hours else 0
payed_hours = int(payed_hours)
happiness = int(happiness)
date = datetime.strptime(f"{day} {month} {year}", "%d %B %Y")
self.data[date.strftime("%Y-%m-%d")] = {
"payed_hours": payed_hours,
"volonteer_hours": volonteer_hours,
"volunteer_hours": volunteer_hours,
"happyness": happiness,
}
@ -67,10 +68,28 @@ class WorklogPreprocessor(Preprocessor):
new_lines.append(f"## 🗓️ {day_of_week} {day} {month} {year}")
else:
new_lines.append(line)
else:
new_lines.append(line)
return new_lines
def compute_data(self, metadata):
done_hours = sum([item["payed_hours"] for item in self.data.values()])
data = dict(
data=self.data,
done_hours=done_hours,
template="worklog",
)
if "total_days" in metadata:
total_hours = int(metadata["total_days"]) * 7
data.update(
dict(
total_hours=total_hours,
percentage=round(done_hours / total_hours * 100),
)
)
return data
class SimpleReader(MarkdownReader):
enabled = True
@ -85,8 +104,10 @@ class SimpleReader(MarkdownReader):
self._source_path = source_path
self._md = Markdown(**self.settings["MARKDOWN"])
is_worklog = Path(source_path).parent.match("pages/worklog")
if is_worklog:
worklog = WorklogPreprocessor(self._md)
if "worklog" in source_path:
self._md.preprocessors.register(worklog, "worklog", 20)
with pelican_open(source_path) as text:
@ -98,16 +119,8 @@ class SimpleReader(MarkdownReader):
metadata = {}
# Add the worklog info to the metadata
if "worklog" in source_path:
done_hours = sum([item["payed_hours"] for item in worklog.data.values()])
total_hours = int(metadata["total_days"]) * 8
metadata["worklog"] = {
"data": worklog.data,
"total_hours": total_hours,
"done_hours": done_hours,
"percentage": round(done_hours / total_hours * 100),
}
print(metadata)
if is_worklog:
metadata["worklog"] = worklog.compute_data(metadata)
# Add the TOC to the metadata.
if len(self._md.toc) > 300: