Skip to content

Commit 82c9232

Browse files
Merge branch 'develop' into 'fb-optic-1178/memory-leak'
Workflow run: https://github.com/HumanSignal/label-studio/actions/runs/12356998670
2 parents 6f14092 + b876970 commit 82c9232

File tree

17 files changed

+172
-19
lines changed

17 files changed

+172
-19
lines changed

label_studio/core/all_urls.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@
149149
"name": "projects:api:project-detail",
150150
"decorators": ""
151151
},
152+
{
153+
"url": "/api/projects/counts/",
154+
"module": "projects.api.ProjectCountsListAPI",
155+
"name": "projects:api:project-counts-list",
156+
"decorators": ""
157+
},
152158
{
153159
"url": "/api/projects/<int:pk>/next/",
154160
"module": "projects.api.ProjectNextTaskAPI",

label_studio/feature_flags.json

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,6 +2418,33 @@
24182418
"version": 4,
24192419
"deleted": false
24202420
},
2421+
"fflag_feat_front_dia_1747_projects_list_banner": {
2422+
"key": "fflag_feat_front_dia_1747_projects_list_banner",
2423+
"on": false,
2424+
"prerequisites": [],
2425+
"targets": [],
2426+
"contextTargets": [],
2427+
"rules": [],
2428+
"fallthrough": {
2429+
"variation": 0
2430+
},
2431+
"offVariation": 1,
2432+
"variations": [
2433+
true,
2434+
false
2435+
],
2436+
"clientSideAvailability": {
2437+
"usingMobileKey": false,
2438+
"usingEnvironmentId": false
2439+
},
2440+
"clientSide": false,
2441+
"salt": "21bfa474c82b4ab0b89314fbecd164cc",
2442+
"trackEvents": false,
2443+
"trackEventsFallthrough": false,
2444+
"debugEventsUntilDate": null,
2445+
"version": 3,
2446+
"deleted": false
2447+
},
24212448
"fflag_feat_front_dia_916_model_creation_and_versioning_short": {
24222449
"key": "fflag_feat_front_dia_916_model_creation_and_versioning_short",
24232450
"on": true,
@@ -3036,7 +3063,7 @@
30363063
"trackEvents": false,
30373064
"trackEventsFallthrough": false,
30383065
"debugEventsUntilDate": null,
3039-
"version": 2,
3066+
"version": 3,
30403067
"deleted": false
30413068
},
30423069
"fflag_feat_front_optic_1419_backend_csv_comments_export_short": {
@@ -4778,7 +4805,7 @@
47784805
"trackEvents": false,
47794806
"trackEventsFallthrough": false,
47804807
"debugEventsUntilDate": null,
4781-
"version": 2,
4808+
"version": 3,
47824809
"deleted": false
47834810
},
47844811
"fflag_fix_leap_246_multi_object_hotkeys_160124_short": {

label_studio/projects/api.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from projects.models import Project, ProjectImport, ProjectManager, ProjectReimport, ProjectSummary
3030
from projects.serializers import (
3131
GetFieldsSerializer,
32+
ProjectCountsSerializer,
3233
ProjectImportSerializer,
3334
ProjectLabelConfigSerializer,
3435
ProjectModelVersionExtendedSerializer,
@@ -281,6 +282,36 @@ def post(self, request, *args, **kwargs):
281282
return super(ProjectListAPI, self).post(request, *args, **kwargs)
282283

283284

285+
@method_decorator(
286+
name='get',
287+
decorator=swagger_auto_schema(
288+
tags=['Projects'],
289+
x_fern_sdk_group_name='projects',
290+
x_fern_sdk_method_name='counts',
291+
x_fern_audiences=['public'],
292+
x_fern_pagination={
293+
'offset': '$request.page',
294+
'results': '$response.results',
295+
},
296+
operation_summary="List project's counts",
297+
operation_description='Returns a list of projects with their counts. For example, task_number which is the total task number in project',
298+
),
299+
)
300+
class ProjectCountsListAPI(generics.ListAPIView):
301+
serializer_class = ProjectCountsSerializer
302+
filterset_class = ProjectFilterSet
303+
permission_required = ViewClassPermission(
304+
GET=all_permissions.projects_view,
305+
)
306+
pagination_class = ProjectListPagination
307+
308+
def get_queryset(self):
309+
serializer = GetFieldsSerializer(data=self.request.query_params)
310+
serializer.is_valid(raise_exception=True)
311+
fields = serializer.validated_data.get('include')
312+
return Project.objects.with_counts(fields=fields).filter(organization=self.request.user.active_organization)
313+
314+
284315
@method_decorator(
285316
name='get',
286317
decorator=swagger_auto_schema(

label_studio/projects/serializers.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,22 @@ def get_queue_done(self, project):
297297
return result
298298

299299

300+
class ProjectCountsSerializer(ProjectSerializer):
301+
class Meta:
302+
model = Project
303+
fields = [
304+
'id',
305+
'task_number',
306+
'finished_task_number',
307+
'total_predictions_number',
308+
'total_annotations_number',
309+
'num_tasks_with_annotations',
310+
'useful_annotation_number',
311+
'ground_truth_number',
312+
'skipped_annotations_number',
313+
]
314+
315+
300316
class ProjectOnboardingSerializer(serializers.ModelSerializer):
301317
class Meta:
302318
model = ProjectOnboarding

label_studio/projects/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# CRUD
2020
path('', api.ProjectListAPI.as_view(), name='project-list'),
2121
path('<int:pk>/', api.ProjectAPI.as_view(), name='project-detail'),
22+
path('counts/', api.ProjectCountsListAPI.as_view(), name='project-counts-list'),
2223
# Get next task
2324
path('<int:pk>/next/', api.ProjectNextTaskAPI.as_view(), name='project-next'),
2425
# Label stream history

label_studio/tests/test_project.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
import pytest
44
from django.db.models.query import QuerySet
5+
from django.test import TestCase
6+
from django.urls import reverse
7+
from django.utils.http import urlencode
8+
from organizations.models import Organization
9+
from projects.models import Project
10+
from rest_framework.test import APIClient
11+
from tasks.models import Task
512
from tests.utils import make_project
613
from users.models import User
714

@@ -40,3 +47,45 @@ def test_project_all_members(business_client):
4047

4148
assert isinstance(members, QuerySet)
4249
assert isinstance(members.first(), User)
50+
51+
52+
class TestProjectCountsListAPI(TestCase):
53+
@classmethod
54+
def setUpTestData(cls):
55+
cls.user = User.objects.create_user(username='testuser', email='[email protected]', password='testpassword')
56+
cls.organization = Organization.objects.create(title='testorganization')
57+
cls.user.active_organization = cls.organization
58+
cls.user.save()
59+
cls.project_1 = Project.objects.create(title='Project 1', organization=cls.organization)
60+
cls.project_2 = Project.objects.create(title='Project 2', organization=cls.organization)
61+
Task.objects.create(project=cls.project_1, data={'text': 'Task 1'})
62+
Task.objects.create(project=cls.project_1, data={'text': 'Task 2'})
63+
Task.objects.create(project=cls.project_2, data={'text': 'Task 3'})
64+
65+
def get_url(self, **params):
66+
return f'{reverse("projects:api:project-counts-list")}?{urlencode(params)}'
67+
68+
def test_get_counts(self):
69+
70+
client = APIClient()
71+
client.force_authenticate(user=self.user)
72+
response = client.get(self.get_url(include='id,task_number,finished_task_number,total_predictions_number'))
73+
self.assertEqual(response.status_code, 200)
74+
self.assertEqual(response.json()['count'], 2)
75+
self.assertEqual(
76+
response.json()['results'],
77+
[
78+
{
79+
'id': self.project_1.id,
80+
'task_number': 2,
81+
'finished_task_number': 0,
82+
'total_predictions_number': 0,
83+
},
84+
{
85+
'id': self.project_2.id,
86+
'task_number': 1,
87+
'finished_task_number': 0,
88+
'total_predictions_number': 0,
89+
},
90+
],
91+
)

web/dist/apps/labelstudio/814.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/814.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/main.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/main.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/vendor.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/dist/apps/labelstudio/vendor.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"message": "chore: OPTIC-1337: Update tooltips (#6737)",
3-
"commit": "1747ab0387b57ca5eb91e31288f25e69ff905630",
4-
"date": "2024-12-11T13:08:10.000Z",
2+
"message": "fix: OPTIC-1418: Improve Organization Membership API usage to reduce latency (#6780)",
3+
"commit": "ca1da7a2dc34dfd512d3d1dd71bf722ef4019f66",
4+
"date": "2024-12-16T15:03:14.000Z",
55
"branch": "develop"
66
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"message": "chore: OPTIC-1337: Update tooltips (#6737)",
3-
"commit": "1747ab0387b57ca5eb91e31288f25e69ff905630",
4-
"date": "2024-12-11T13:08:10.000Z",
2+
"message": "fix: OPTIC-1418: Improve Organization Membership API usage to reduce latency (#6780)",
3+
"commit": "ca1da7a2dc34dfd512d3d1dd71bf722ef4019f66",
4+
"date": "2024-12-16T15:03:14.000Z",
55
"branch": "develop"
66
}

web/dist/libs/editor/version.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"message": "chore: OPTIC-1337: Update tooltips (#6737)",
3-
"commit": "1747ab0387b57ca5eb91e31288f25e69ff905630",
4-
"date": "2024-12-11T13:08:10.000Z",
2+
"message": "fix: OPTIC-1418: Improve Organization Membership API usage to reduce latency (#6780)",
3+
"commit": "ca1da7a2dc34dfd512d3d1dd71bf722ef4019f66",
4+
"date": "2024-12-16T15:03:14.000Z",
55
"branch": "develop"
66
}

web/libs/datamanager/src/stores/AppStore.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ export const AppStore = types
479479
}),
480480

481481
fetchUsers: flow(function* () {
482-
const list = yield self.apiCall("users");
482+
const list = yield self.apiCall("users", { __useQueryCache: 60 * 1000 });
483483

484484
self.users.push(...list);
485485
}),

web/libs/datamanager/src/utils/api-proxy/index.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,26 @@ export class APIProxy {
155155
let responseMeta;
156156
const alwaysExpectJSON = options?.alwaysExpectJSON === undefined ? true : options.alwaysExpectJSON;
157157

158+
let shouldUseQueryCache = false;
158159
try {
159160
const finalParams = {
160161
...(methodSettings.params ?? {}),
161162
...(urlParams ?? {}),
162163
...(this.sharedParams ?? {}),
163164
};
164165

166+
if (finalParams.__useQueryCache && methodSettings.queryCache) {
167+
shouldUseQueryCache = true;
168+
169+
const cachedData = methodSettings.queryCache(finalParams);
170+
171+
if (cachedData) {
172+
return cachedData;
173+
}
174+
175+
delete finalParams.__useQueryCache;
176+
}
177+
165178
const { method, url: apiCallURL } = this.createUrl(
166179
methodSettings.path,
167180
finalParams,
@@ -247,7 +260,13 @@ export class APIProxy {
247260
: { ok: true };
248261

249262
if (methodSettings.convert instanceof Function) {
250-
return await methodSettings.convert(responseData);
263+
const convertedData = await methodSettings.convert(responseData);
264+
265+
if (shouldUseQueryCache) {
266+
methodSettings.queryCache(finalParams, convertedData);
267+
}
268+
269+
return convertedData;
251270
}
252271

253272
responseResult = responseData;
@@ -268,6 +287,10 @@ export class APIProxy {
268287
writable: false,
269288
});
270289

290+
if (shouldUseQueryCache) {
291+
methodSettings.queryCache(finalParams, responseResult);
292+
}
293+
271294
return responseResult;
272295
};
273296
}

0 commit comments

Comments
 (0)