Compare commits
8 Commits
v3.8.0
...
v3.8.2-preprod
| Author | SHA1 | Date | |
|---|---|---|---|
| a11258f778 | |||
| 33647f124f | |||
| e339cda5c6 | |||
| 4ce65c654f | |||
| c048b2ae95 | |||
| 5908afb098 | |||
| e2298a3658 | |||
| 278eb233e9 |
+24
-1
@@ -6,6 +6,27 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.8.2] - 2025-10-17
|
||||
|
||||
### Fixed
|
||||
|
||||
🐛(service-worker) fix sw registration and page reload logic #1500
|
||||
|
||||
## [3.8.1] - 2025-10-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- ⚡️(backend) improve trashbin endpoint performance #1495
|
||||
- 🐛(backend) manage invitation partial update without email #1494
|
||||
- ♿(frontend) improve accessibility:
|
||||
- ♿ add missing aria-label to add sub-doc button for accessibility #1480
|
||||
- ♿ add missing aria-label to more options button on sub-docs #1481
|
||||
|
||||
### Removed
|
||||
|
||||
- 🔥(backend) remove treebeard form for the document admin #1470
|
||||
|
||||
|
||||
## [3.8.0] - 2025-10-14
|
||||
|
||||
### Added
|
||||
@@ -788,7 +809,9 @@ and this project adheres to
|
||||
- ✨(frontend) Coming Soon page (#67)
|
||||
- 🚀 Impress, project to manage your documents easily and collaboratively.
|
||||
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v3.8.0...main
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v3.8.2...main
|
||||
[v3.8.2]: https://github.com/suitenumerique/docs/releases/v3.8.2
|
||||
[v3.8.1]: https://github.com/suitenumerique/docs/releases/v3.8.1
|
||||
[v3.8.0]: https://github.com/suitenumerique/docs/releases/v3.8.0
|
||||
[v3.7.0]: https://github.com/suitenumerique/docs/releases/v3.7.0
|
||||
[v3.6.0]: https://github.com/suitenumerique/docs/releases/v3.6.0
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.contrib.auth import admin as auth_admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from treebeard.admin import TreeAdmin
|
||||
from treebeard.forms import movenodeform_factory
|
||||
|
||||
from . import models
|
||||
|
||||
@@ -157,7 +156,6 @@ class DocumentAdmin(TreeAdmin):
|
||||
},
|
||||
),
|
||||
)
|
||||
form = movenodeform_factory(models.Document)
|
||||
inlines = (DocumentAccessInline,)
|
||||
list_display = (
|
||||
"id",
|
||||
|
||||
@@ -749,7 +749,8 @@ class InvitationSerializer(serializers.ModelSerializer):
|
||||
if self.instance is None:
|
||||
attrs["issuer"] = user
|
||||
|
||||
attrs["email"] = attrs["email"].lower()
|
||||
if attrs.get("email"):
|
||||
attrs["email"] = attrs["email"].lower()
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
@@ -623,12 +623,29 @@ class DocumentViewSet(
|
||||
The selected documents are those deleted within the cutoff period defined in the
|
||||
settings (see TRASHBIN_CUTOFF_DAYS), before they are considered permanently deleted.
|
||||
"""
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.get_response_for_queryset(self.queryset.none())
|
||||
|
||||
access_documents_paths = (
|
||||
models.DocumentAccess.objects.select_related("document")
|
||||
.filter(
|
||||
db.Q(user=self.request.user) | db.Q(team__in=self.request.user.teams),
|
||||
role=models.RoleChoices.OWNER,
|
||||
)
|
||||
.values_list("document__path", flat=True)
|
||||
)
|
||||
|
||||
children_clause = db.Q()
|
||||
for path in access_documents_paths:
|
||||
children_clause |= db.Q(path__startswith=path)
|
||||
|
||||
queryset = self.queryset.filter(
|
||||
children_clause,
|
||||
deleted_at__isnull=False,
|
||||
deleted_at__gte=models.get_trashbin_cutoff(),
|
||||
)
|
||||
queryset = queryset.annotate_user_roles(self.request.user)
|
||||
queryset = queryset.filter(user_roles__contains=[models.RoleChoices.OWNER])
|
||||
|
||||
return self.get_response_for_queryset(queryset)
|
||||
|
||||
|
||||
@@ -769,6 +769,37 @@ def test_api_document_invitations_update_authenticated_unprivileged(
|
||||
assert value == old_invitation_values[key]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
@pytest.mark.parametrize("role", ["administrator", "owner"])
|
||||
def test_api_document_invitations_patch(via, role, mock_user_teams):
|
||||
"""Partially updating an invitation should be allowed."""
|
||||
|
||||
user = factories.UserFactory()
|
||||
invitation = factories.InvitationFactory(role="editor")
|
||||
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(
|
||||
document=invitation.document, user=user, role=role
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=invitation.document, team="lasuite", role=role
|
||||
)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{invitation.document.id!s}/invitations/{invitation.id!s}/",
|
||||
{"role": "reader"},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
invitation.refresh_from_db()
|
||||
assert invitation.role == "reader"
|
||||
|
||||
|
||||
# Delete
|
||||
|
||||
|
||||
|
||||
@@ -166,10 +166,10 @@ def test_api_documents_trashbin_authenticated_direct(django_assert_num_queries):
|
||||
|
||||
expected_ids = {str(document1.id), str(document2.id), str(document3.id)}
|
||||
|
||||
with django_assert_num_queries(10):
|
||||
with django_assert_num_queries(11):
|
||||
response = client.get("/api/v1.0/documents/trashbin/")
|
||||
|
||||
with django_assert_num_queries(4):
|
||||
with django_assert_num_queries(5):
|
||||
response = client.get("/api/v1.0/documents/trashbin/")
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -208,10 +208,10 @@ def test_api_documents_trashbin_authenticated_via_team(
|
||||
|
||||
expected_ids = {str(deleted_document_team1.id), str(deleted_document_team2.id)}
|
||||
|
||||
with django_assert_num_queries(7):
|
||||
with django_assert_num_queries(8):
|
||||
response = client.get("/api/v1.0/documents/trashbin/")
|
||||
|
||||
with django_assert_num_queries(3):
|
||||
with django_assert_num_queries(4):
|
||||
response = client.get("/api/v1.0/documents/trashbin/")
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "3.8.0"
|
||||
version = "3.8.2"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -202,9 +202,19 @@ test.describe('Document create member', () => {
|
||||
);
|
||||
await expect(userInvitation).toBeVisible();
|
||||
|
||||
const responsePromisePatchInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes('/invitations/') &&
|
||||
response.status() === 200 &&
|
||||
response.request().method() === 'PATCH',
|
||||
);
|
||||
|
||||
await userInvitation.getByLabel('doc-role-dropdown').click();
|
||||
await page.getByRole('menuitem', { name: 'Reader' }).click();
|
||||
|
||||
const responsePatchInvitation = await responsePromisePatchInvitation;
|
||||
expect(responsePatchInvitation.ok()).toBeTruthy();
|
||||
|
||||
const moreActions = userInvitation.getByRole('button', {
|
||||
name: 'Open invitation actions menu',
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
|
||||
+2
-2
@@ -152,7 +152,6 @@ export const DocTreeItemActions = ({
|
||||
options={options}
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
aria-label={t('Open document actions menu')}
|
||||
>
|
||||
<Icon
|
||||
onClick={(e) => {
|
||||
@@ -164,7 +163,7 @@ export const DocTreeItemActions = ({
|
||||
variant="filled"
|
||||
$theme="primary"
|
||||
$variation="600"
|
||||
aria-hidden="true"
|
||||
aria-label={t('More options')}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
{doc.abilities.children_create && (
|
||||
@@ -178,6 +177,7 @@ export const DocTreeItemActions = ({
|
||||
});
|
||||
}}
|
||||
color="primary"
|
||||
aria-label={t('Add a sub page')}
|
||||
data-testid="doc-tree-item-actions-add-child"
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -3,51 +3,69 @@ import { useEffect } from 'react';
|
||||
export const useSWRegister = () => {
|
||||
useEffect(() => {
|
||||
if (
|
||||
'serviceWorker' in navigator &&
|
||||
process.env.NEXT_PUBLIC_SW_DEACTIVATED !== 'true'
|
||||
!('serviceWorker' in navigator) ||
|
||||
process.env.NEXT_PUBLIC_SW_DEACTIVATED === 'true'
|
||||
) {
|
||||
navigator.serviceWorker
|
||||
.register(`/service-worker.js`)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const newWorker = registration.installing;
|
||||
if (!newWorker) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
const hadControllerAtStart = !!navigator.serviceWorker.controller;
|
||||
|
||||
navigator.serviceWorker
|
||||
.register(`/service-worker.js`)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const newWorker = registration.installing;
|
||||
if (!newWorker) {
|
||||
return;
|
||||
}
|
||||
|
||||
newWorker.onstatechange = () => {
|
||||
if (
|
||||
newWorker.state === 'installed' &&
|
||||
navigator.serviceWorker.controller
|
||||
) {
|
||||
newWorker.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
|
||||
newWorker.onstatechange = () => {
|
||||
if (
|
||||
newWorker.state === 'installed' &&
|
||||
navigator.serviceWorker.controller
|
||||
) {
|
||||
newWorker.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Service worker registration failed:', err);
|
||||
});
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Service worker registration failed:', err);
|
||||
});
|
||||
|
||||
let refreshing = false;
|
||||
const onControllerChange = () => {
|
||||
if (refreshing) {
|
||||
return;
|
||||
}
|
||||
refreshing = true;
|
||||
let refreshing = false;
|
||||
const onControllerChange = () => {
|
||||
if (!hadControllerAtStart || refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshing = true;
|
||||
|
||||
if (document.visibilityState === 'visible') {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const onVisible = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
navigator.serviceWorker.addEventListener(
|
||||
|
||||
document.addEventListener('visibilitychange', onVisible, { once: true });
|
||||
};
|
||||
|
||||
navigator.serviceWorker.addEventListener(
|
||||
'controllerchange',
|
||||
onControllerChange,
|
||||
);
|
||||
|
||||
return () => {
|
||||
navigator.serviceWorker.removeEventListener(
|
||||
'controllerchange',
|
||||
onControllerChange,
|
||||
);
|
||||
|
||||
return () => {
|
||||
navigator.serviceWorker.removeEventListener(
|
||||
'controllerchange',
|
||||
onControllerChange,
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impress",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"private": true,
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint-plugin-docs",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "packages-i18n",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server-y-provider",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"description": "Y.js provider for docs",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
environments:
|
||||
dev:
|
||||
values:
|
||||
- version: 3.8.0
|
||||
- version: 3.8.2
|
||||
feature:
|
||||
values:
|
||||
- version: 3.8.0
|
||||
- version: 3.8.2
|
||||
feature: ci
|
||||
domain: example.com
|
||||
imageTag: demo
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
type: application
|
||||
name: docs
|
||||
version: 3.8.0
|
||||
version: 3.8.2
|
||||
appVersion: latest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mail_mjml",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.2",
|
||||
"description": "An util to generate html and text django's templates from mjml templates",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user