Add WAGTAIL_EDITING_SESSION_PING_INTERVAL setting

pull/12185/head
Sage Abdullah 2024-07-19 11:30:48 +01:00 zatwierdzone przez Thibaud Colas
rodzic 78cfab1662
commit f8564055b1
6 zmienionych plików z 201 dodań i 2 usunięć

Wyświetl plik

@ -67,6 +67,17 @@ describe('SessionController', () => {
handlePing.mockClear();
jest.advanceTimersByTime(123456);
expect(handlePing).toHaveBeenCalledTimes(6);
// Setting it to 0 should stop the interval
handlePing.mockClear();
element.setAttribute('data-w-session-interval-value', '0');
await Promise.resolve();
jest.advanceTimersByTime(20000);
expect(handlePing).toHaveBeenCalledTimes(0);
jest.advanceTimersByTime(20000);
expect(handlePing).toHaveBeenCalledTimes(0);
handlePing.mockClear();
});
it('should allow setting a custom interval value on init and changing it afterwards', async () => {
@ -100,6 +111,17 @@ describe('SessionController', () => {
handlePing.mockClear();
jest.advanceTimersByTime(123456);
expect(handlePing).toHaveBeenCalledTimes(8);
// Setting it to >= 2**31 should stop the interval
handlePing.mockClear();
element.setAttribute('data-w-session-interval-value', `${2 ** 31}`);
await Promise.resolve();
jest.advanceTimersByTime(20000);
expect(handlePing).toHaveBeenCalledTimes(0);
jest.advanceTimersByTime(20000);
expect(handlePing).toHaveBeenCalledTimes(0);
handlePing.mockClear();
});
});

Wyświetl plik

@ -128,6 +128,10 @@ export class SessionController extends Controller<HTMLElement> {
*/
addInterval(): void {
this.clearInterval();
// Values outside this range will be ignored by window.setInterval,
// making it fire all the time.
if (this.intervalValue <= 0 || this.intervalValue >= 2 ** 31) return;
this.interval = window.setInterval(this.ping, this.intervalValue);
}

Wyświetl plik

@ -259,6 +259,16 @@ WAGTAIL_AUTO_UPDATE_PREVIEW_INTERVAL = 500
The interval (in milliseconds) is to check for changes made in the page editor before updating the preview. The default value is `500`.
(wagtail_editing_session_ping_interval)=
### `WAGTAIL_EDITING_SESSION_PING_INTERVAL`
```python
WAGTAIL_EDITING_SESSION_PING_INTERVAL = 10000
```
The interval (in milliseconds) to ping the server during an editing session. This is used to indicate that the session is active, as well as to display the list of other sessions that are currently editing the same content. The default value is `10000` (10 seconds). In order to effectively display the sessions list, this value needs to be set to under 1 minute. If set to 0, the interval will be disabled.
(wagtailadmin_global_edit_lock)=
### `WAGTAILADMIN_GLOBAL_EDIT_LOCK`

Wyświetl plik

@ -29,6 +29,7 @@
data-w-swap-defer-value="true"
data-w-action-continue-value="true"
data-w-action-url-value="{{ release_url }}"
data-w-session-interval-value="{{ ping_interval }}"
data-w-session-w-dialog-outlet="[data-edit-form] [data-controller='w-dialog']#w-overwrite-changes-dialog"
data-action="w-session:ping->w-swap#submit visibilitychange@document->w-session#dispatchVisibilityState w-session:visible->w-session#ping w-session:visible->w-session#addInterval w-session:hidden->w-session#clearInterval w-session:hidden->w-action#sendBeacon w-unsaved:add@document->w-session#setUnsavedChanges w-unsaved:clear@document->w-session#setUnsavedChanges w-swap:json->w-session#updateSessionData"
>

Wyświetl plik

@ -1,16 +1,22 @@
import datetime
from django.conf import settings
from django.contrib.admin.utils import quote
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import timezone
from freezegun import freeze_time
from wagtail.admin.models import EditingSession
from wagtail.models import GroupPagePermission, Page
from wagtail.test.testapp.models import Advert, SimplePage
from wagtail.test.testapp.models import (
Advert,
AdvertWithCustomPrimaryKey,
FullFeaturedSnippet,
SimplePage,
)
from wagtail.test.utils import WagtailTestUtils
if settings.USE_TZ:
@ -1180,3 +1186,151 @@ class TestReleaseView(WagtailTestUtils, TestCase):
self.assertTrue(
EditingSession.objects.filter(id=self.other_session.id).exists()
)
class TestModuleInEditView(WagtailTestUtils, TestCase):
url_name = "wagtailadmin_pages:edit"
model = Page
def setUp(self):
self.user = self.create_superuser(
"bob", password="password", first_name="Bob", last_name="Testuser"
)
self.login(user=self.user)
self.content_type = ContentType.objects.get_for_model(self.model)
self.object = self.create_object()
self.session = EditingSession.objects.create(
user=self.user,
content_type=self.content_type,
object_id=self.object.pk,
last_seen_at=TIMESTAMP_1,
)
self.old_session = EditingSession.objects.create(
user=self.user,
content_type=self.content_type,
object_id=self.object.pk,
last_seen_at=TIMESTAMP_PAST,
)
def create_object(self):
root_page = Page.get_first_root_node()
page = SimplePage(title="Foo", slug="foo", content="bar")
root_page.add_child(instance=page)
page.save_revision()
return page
def get(self):
return self.client.get(reverse(self.url_name, args=(quote(self.object.pk),)))
def assertRevisionInput(self, soup):
revision_input = soup.select_one('input[name="revision_id"]')
self.assertIsNotNone(revision_input)
self.assertEqual(revision_input.get("type"), "hidden")
self.assertEqual(
revision_input.get("value"),
str(self.object.latest_revision.id),
)
@freeze_time(TIMESTAMP_NOW)
def test_edit_view_with_default_interval(self):
self.assertEqual(EditingSession.objects.all().count(), 2)
response = self.get()
self.assertEqual(response.status_code, 200)
# Should perform a cleanup of the EditingSessions
self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
self.assertFalse(EditingSession.objects.filter(id=self.old_session.id).exists())
# Should create a new EditingSession for the current user
self.assertEqual(EditingSession.objects.all().count(), 2)
new_session = EditingSession.objects.exclude(id=self.session.id).get(
content_type=self.content_type,
object_id=self.object.pk,
)
self.assertEqual(new_session.user, self.user)
# Should load the EditingSessionsModule with the default interval (10s)
soup = self.get_soup(response.content)
module = soup.select_one('form[data-controller~="w-session"]')
self.assertIsNotNone(module)
self.assertEqual(module.get("data-w-session-interval-value"), "10000")
# Should show the revision_id input
self.assertRevisionInput(module)
@freeze_time(TIMESTAMP_NOW)
@override_settings(WAGTAIL_EDITING_SESSION_PING_INTERVAL=30000)
def test_edit_view_with_custom_interval(self):
self.assertEqual(EditingSession.objects.all().count(), 2)
response = self.get()
self.assertEqual(response.status_code, 200)
# Should perform a cleanup of the EditingSessions
self.assertTrue(EditingSession.objects.filter(id=self.session.id).exists())
self.assertFalse(EditingSession.objects.filter(id=self.old_session.id).exists())
# Should create a new EditingSession for the current user
self.assertEqual(EditingSession.objects.all().count(), 2)
new_session = EditingSession.objects.exclude(id=self.session.id).get(
content_type=self.content_type,
object_id=self.object.pk,
)
self.assertEqual(new_session.user, self.user)
# Should load the EditingSessionsModule
soup = self.get_soup(response.content)
module = soup.select_one('form[data-controller~="w-session"]')
self.assertIsNotNone(module)
self.assertEqual(
module.get("data-w-swap-src-value"),
reverse(
"wagtailadmin_editing_sessions:ping",
args=(
self.content_type.app_label,
self.content_type.model,
quote(self.object.pk),
new_session.id,
),
),
)
self.assertEqual(
module.get("data-w-action-url-value"),
reverse(
"wagtailadmin_editing_sessions:release",
args=(new_session.id,),
),
)
# Should use the custom interval (30s)
self.assertEqual(module.get("data-w-session-interval-value"), "30000")
self.assertRevisionInput(module)
class TestModuleInEditViewWithRevisableSnippet(TestModuleInEditView):
model = FullFeaturedSnippet
@property
def url_name(self):
return self.model.snippet_viewset.get_url_name("edit")
def create_object(self):
obj = self.model.objects.create(text="Shodan")
obj.save_revision()
return obj
class TestModuleInEditViewWithNonRevisableSnippet(TestModuleInEditView):
model = AdvertWithCustomPrimaryKey
@property
def url_name(self):
return self.model.snippet_viewset.get_url_name("edit")
def create_object(self):
return self.model.objects.create(text="GLaDOS", advert_id="m0n5t3r!/#")
def assertRevisionInput(self, soup):
revision_input = soup.select_one('input[name="revision_id"]')
self.assertIsNone(revision_input)

Wyświetl plik

@ -1,3 +1,5 @@
from django.conf import settings
from wagtail.admin.ui.components import Component
@ -23,10 +25,16 @@ class EditingSessionsModule(Component):
self.revision_id = revision_id
def get_context_data(self, parent_context):
ping_interval = getattr(
settings,
"WAGTAIL_EDITING_SESSION_PING_INTERVAL",
10000,
)
return {
"current_session": self.current_session,
"ping_url": self.ping_url,
"release_url": self.release_url,
"ping_interval": str(ping_interval), # avoid the need to | unlocalize
"sessions_list": self.sessions_list,
"content_type": self.content_type,
"revision_id": self.revision_id,