feat: 🎸 get the dataset state, and backfill the missing parts
Creates two new endpoints:

- `GET /admin/dataset-state?dataset=[dataset]`: get the current state of the dataset (queue and cache)
- `POST /admin/dataset-backfill?dataset=[dataset]`: create (and delete) the jobs required to fix a dataset if necessary

They rely on the new class `DatasetState`. Please review the details of the implementation.

---

Here is a screencast of the two endpoints.

https://user-images.githubusercontent.com/1676121/232551041-4a12f9b9-490d-4d4b-b63a-ad7dbfe68e4b.mov

---

Not to be discussed here: as a follow-up, I think that most of the logic of the queue and DAG should go through that class, within a small orchestrator, instead of through the job runners.

- currently: when a job runner finishes a job, it tries to create the following jobs in the DAG order.
- proposal: when a job runner finishes a job, it sends the result to the orchestrator, that computes the dataset state and launches the missing jobs.
feat: 🎸 get the dataset state, and backfill the missing parts: Creates two new endpoints:

- `GET /admin/dataset-state?dataset=[dataset]`: get the current state of the dataset (queue and cache)
- `POST /admin/dataset-backfill?dataset=[dataset]`: create (and delete) the jobs required to fix a dataset if necessary

They rely on the new class `DatasetState`. Please review the details of the implementation.

---

Here is a screencast of the two endpoints.

https://user-images.githubusercontent.com/1676121/232551041-4a12f9b9-490d-4d4b-b63a-ad7dbfe68e4b.mov

---

Not to be discussed here: as a follow-up, I think that most of the logic of the queue and DAG should go through that class, within a small orchestrator, instead of through the job runners.

- currently: when a job runner finishes a job, it tries to create the following jobs in the DAG order.
- proposal: when a job runner finishes a job, it sends the result to the orchestrator, that computes the dataset state and launches the missing jobs.