-
Notifications
You must be signed in to change notification settings - Fork 2.8k
BROS-114: Global Custom Hotkeys #7784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
deppp
wants to merge
15
commits into
develop
Choose a base branch
from
fb-bros-114
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
8e1a3db
Merge branch 'develop' into dev/custom-hotkeys
7364d75
implementation of custom hotkeys, most of the code is around customiz…
1ebf49e
cleaning up the code
f87f795
fixing small bugs
90fc2cf
remove duplicate check
960aecc
adding tests
b578642
Merge branch 'develop' into fb-bros-114
b8b94df
Merge remote-tracking branch 'origin/develop' into fb-bros-114
63ba874
removing wrong import
3e3a70a
documenting hotkeys POST api with swagger
8c034be
adding missing shad components
48f881c
adding radix-switch
a462448
Update lock
6542861
Update label_studio/users/api.py
deppp 22f99ff
Update label_studio/users/functions.py
deppp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Generated by Django 5.1.9 on 2025-06-03 23:29 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("users", "0010_userproducttour"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="user", | ||
name="custom_hotkeys", | ||
field=models.JSONField(blank=True, default=dict, null=True), | ||
), | ||
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -90,7 +90,8 @@ class User(UserMixin, AbstractBaseUser, PermissionsMixin, UserLastActivityMixin) | |||||||||||||||||
last_name = models.CharField(_('last name'), max_length=256, blank=True) | ||||||||||||||||||
phone = models.CharField(_('phone'), max_length=256, blank=True) | ||||||||||||||||||
avatar = models.ImageField(upload_to=hash_upload, blank=True) | ||||||||||||||||||
|
||||||||||||||||||
custom_hotkeys = models.JSONField(default=dict, blank=True, null=True) | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
is_staff = models.BooleanField( | ||||||||||||||||||
_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.') | ||||||||||||||||||
) | ||||||||||||||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
|
||
import json | ||
from django.test import TestCase | ||
from django.urls import reverse | ||
from rest_framework.test import APIClient | ||
from rest_framework import status | ||
from django.contrib.auth import get_user_model | ||
|
||
User = get_user_model() | ||
|
||
class UserHotkeysAPITestCase(TestCase): | ||
"""Tests for the UserHotkeysAPI""" | ||
|
||
def setUp(self): | ||
self.client = APIClient() | ||
# Create a test user | ||
self.user = User.objects.create_user( | ||
username='testuser', | ||
email='test@example.com', | ||
password='password123' | ||
) | ||
# Set initial hotkeys | ||
self.user.custom_hotkeys = { | ||
"editor:save": {"key": "ctrl+s", "active": True}, | ||
"editor:find": {"key": "ctrl+f", "active": True} | ||
} | ||
self.user.save() | ||
|
||
# URL for the hotkeys API | ||
self.url = reverse('current-user-hotkeys') # Adjust based on your URL configuration | ||
|
||
# Authenticate the test client | ||
self.client.force_authenticate(user=self.user) | ||
|
||
# Valid payload for tests | ||
self.valid_payload = { | ||
"custom_hotkeys": { | ||
"editor:save": {"key": "ctrl+shift+s", "active": True}, | ||
"editor:new": {"key": "ctrl+n", "active": True} | ||
} | ||
} | ||
|
||
def test_update_hotkeys_authenticated(self): | ||
"""Test updating hotkeys for authenticated user""" | ||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(self.valid_payload), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertEqual(response.data['custom_hotkeys'], self.valid_payload['custom_hotkeys']) | ||
|
||
# Verify user data was updated in database | ||
user = User.objects.get(id=self.user.id) | ||
self.assertEqual(user.custom_hotkeys, self.valid_payload['custom_hotkeys']) | ||
|
||
def test_update_hotkeys_unauthenticated(self): | ||
"""Test updating hotkeys fails for unauthenticated user""" | ||
# Logout/un-authenticate the client | ||
self.client.force_authenticate(user=None) | ||
|
||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(self.valid_payload), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) | ||
|
||
def test_update_hotkeys_invalid_data(self): | ||
"""Test updating hotkeys with invalid data""" | ||
invalid_payload = { | ||
"custom_hotkeys": { | ||
"editor:save": {"active": True} # Missing 'key' | ||
} | ||
} | ||
|
||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(invalid_payload), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) | ||
|
||
def test_update_hotkeys_partial(self): | ||
"""Test updating only some hotkeys preserves existing configuration""" | ||
partial_update = { | ||
"custom_hotkeys": { | ||
"editor:save": {"key": "ctrl+alt+s", "active": True} | ||
} | ||
} | ||
|
||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(partial_update), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
# Should completely replace the user's hotkeys, not merge them | ||
user = User.objects.get(id=self.user.id) | ||
self.assertEqual(user.custom_hotkeys, partial_update['custom_hotkeys']) | ||
self.assertNotIn('editor:find', user.custom_hotkeys) | ||
|
||
def test_empty_hotkeys(self): | ||
"""Test setting empty hotkeys dictionary""" | ||
empty_payload = { | ||
"custom_hotkeys": {} | ||
} | ||
|
||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(empty_payload), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
|
||
# User should now have empty hotkeys | ||
user = User.objects.get(id=self.user.id) | ||
self.assertEqual(user.custom_hotkeys, {}) | ||
|
||
def test_missing_required_field(self): | ||
"""Test request with missing required field""" | ||
invalid_payload = {} # Missing 'custom_hotkeys' | ||
|
||
response = self.client.post( | ||
self.url, | ||
data=json.dumps(invalid_payload), | ||
content_type='application/json' | ||
) | ||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not creating
mergedEditorKeymap
in Django code instead of custom inline js ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Django doesn't know much about the defaults for the keys, and we don't want to have two sets of defaults, one on the backend, and another on the frontend, all of the definitions and processing is done at the frontend level, this follows DRY principle.