diff --git a/CHANGELOG.md b/CHANGELOG.md index 196fd36a0e8c..496fff231c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Downloading additional data from cloud storage if use_cache=true and job_file_mapping are specified () +- Leaving an organization () ### Security - TDB diff --git a/cvat-core/src/organization.ts b/cvat-core/src/organization.ts index fcc3379d2150..f9219a127d31 100644 --- a/cvat-core/src/organization.ts +++ b/cvat-core/src/organization.ts @@ -332,7 +332,7 @@ Object.defineProperties(Organization.prototype.leave, { const result = await serverProxy.organizations.members(this.slug, 1, 10, { filter: JSON.stringify({ and: [{ - '==': [{ var: 'user' }, user.id], + '==': [{ var: 'user' }, user.username], }], }), }); diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 5266d6798848..a42d3f25bee8 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.56.1", + "version": "1.56.2", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/organization-actions.ts b/cvat-ui/src/actions/organization-actions.ts index 01fa06eaa42c..b114ac3f5958 100644 --- a/cvat-ui/src/actions/organization-actions.ts +++ b/cvat-ui/src/actions/organization-actions.ts @@ -212,14 +212,17 @@ export function inviteOrganizationMembersAsync( }; } -export function leaveOrganizationAsync(organization: any): ThunkAction { +export function leaveOrganizationAsync( + organization: any, + onLeaveSuccess?: () => void, +): ThunkAction { return async function (dispatch, getState) { const { user } = getState().auth; dispatch(organizationActions.leaveOrganization()); try { await organization.leave(user); dispatch(organizationActions.leaveOrganizationSuccess()); - localStorage.removeItem('currentOrganization'); + if (onLeaveSuccess) onLeaveSuccess(); } catch (error) { dispatch(organizationActions.leaveOrganizationFailed(error)); } diff --git a/cvat-ui/src/components/organization-page/member-item.tsx b/cvat-ui/src/components/organization-page/member-item.tsx index b5a7eba63e33..66acf94fcfed 100644 --- a/cvat-ui/src/components/organization-page/member-item.tsx +++ b/cvat-ui/src/components/organization-page/member-item.tsx @@ -3,12 +3,14 @@ // SPDX-License-Identifier: MIT import React from 'react'; +import { useSelector } from 'react-redux'; import Select from 'antd/lib/select'; import Text from 'antd/lib/typography/Text'; import { Row, Col } from 'antd/lib/grid'; import moment from 'moment'; import { DeleteOutlined } from '@ant-design/icons'; import Modal from 'antd/lib/modal'; +import { CombinedState } from 'reducers'; export interface Props { membershipInstance: any; @@ -24,6 +26,7 @@ function MemberItem(props: Props): JSX.Element { user, joined_date: joinedDate, role, invitation, } = membershipInstance; const { username, firstName, lastName } = user; + const { username: selfUserName } = useSelector((state: CombinedState) => state.auth.user); return ( @@ -61,7 +64,7 @@ function MemberItem(props: Props): JSX.Element { - {role !== 'owner' ? ( + {(role === 'owner' || selfUserName === username) ? null : ( { Modal.confirm({ @@ -78,7 +81,7 @@ function MemberItem(props: Props): JSX.Element { }); }} /> - ) : null} + )} ); diff --git a/cvat-ui/src/components/organization-page/top-bar.tsx b/cvat-ui/src/components/organization-page/top-bar.tsx index 95ada802ec9f..bc4a700ca4b4 100644 --- a/cvat-ui/src/components/organization-page/top-bar.tsx +++ b/cvat-ui/src/components/organization-page/top-bar.tsx @@ -281,7 +281,10 @@ function OrganizationTopBar(props: Props): JSX.Element { onClick={() => { Modal.confirm({ onOk: () => { - dispatch(leaveOrganizationAsync(organizationInstance)); + dispatch(leaveOrganizationAsync(organizationInstance, () => { + localStorage.removeItem('currentOrganization'); + window.location.reload(); + })); }, className: 'cvat-modal-organization-leave-confirm', content: ( diff --git a/cvat/apps/iam/rules/memberships.rego b/cvat/apps/iam/rules/memberships.rego index ec97f4f2ef3c..f64c8da1f91f 100644 --- a/cvat/apps/iam/rules/memberships.rego +++ b/cvat/apps/iam/rules/memberships.rego @@ -71,6 +71,7 @@ allow { input.resource.organization.id == input.auth.organization.id } +# maintainer of the organization can change the role of any member and remove any member except himself/another maintainer/owner allow { { utils.CHANGE_ROLE, utils.DELETE }[input.scope] input.resource.is_active @@ -81,22 +82,28 @@ allow { organizations.OWNER, organizations.MAINTAINER }[input.resource.role] + input.resource.user.id != input.auth.user.id } + +# owner of the organization can change the role of any member and remove any member except himself allow { { utils.CHANGE_ROLE, utils.DELETE }[input.scope] input.resource.is_active input.resource.organization.id == input.auth.organization.id utils.has_perm(utils.USER) organizations.is_owner + input.resource.user.id != input.auth.user.id input.resource.role != organizations.OWNER } +# member can leave the organization except case when member is the owner allow { input.scope == utils.DELETE input.resource.is_active - utils.is_sandbox - input.resource.role != organizations.OWNER + organizations.is_member + input.resource.organization.id == input.auth.organization.id input.resource.user.id == input.auth.user.id + input.resource.role != organizations.OWNER utils.has_perm(utils.WORKER) } diff --git a/cvat/apps/iam/rules/tests/configs/memberships.csv b/cvat/apps/iam/rules/tests/configs/memberships.csv index 0f5592477f7b..2f8a263dd4df 100644 --- a/cvat/apps/iam/rules/tests/configs/memberships.csv +++ b/cvat/apps/iam/rules/tests/configs/memberships.csv @@ -4,8 +4,8 @@ list,Membership,Organization,N/A,,GET,/memberships,None,Worker view,Membership,Sandbox,None,,GET,/membership/{id},Admin,N/A view,Membership,Sandbox,Self,,GET,/membership/{id},None,N/A view,Membership,Organization,"None, Self",,GET,/membership/{id},None,Worker -change:role,Membership,Organization,"None, Self","resource[""role""] not in [""maintainer"", ""owner""]",PATCH,/membership/{id},User,Maintainer -change:role,Membership,Organization,"None, Self","resource[""role""] != ""owner""",PATCH,/membership/{id},User,Owner -delete,Membership,Organization,"None, Self","resource[""role""] not in [""maintainer"", ""owner""]",DELETE,/membership/{id},User,Maintainer -delete,Membership,Organization,"None, Self","resource[""role""] != ""owner""",DELETE,/membership/{id},User,Owner -delete,Membership,Sandbox,Self,"resource[""role""] != ""owner""",DELETE,/membership/{id},Worker,N/A \ No newline at end of file +change:role,Membership,Organization,None,"resource[""role""] != ""owner""",PATCH,/membership/{id},User,Owner +change:role,Membership,Organization,None,"resource[""role""] not in [""maintainer"", ""owner""]",PATCH,/membership/{id},User,Maintainer +delete,Membership,Organization,None,"resource[""role""] != ""owner""",DELETE,/membership/{id},User,Owner +delete,Membership,Organization,None,"resource[""role""] not in [""maintainer"", ""owner""]",DELETE,/membership/{id},User,Maintainer +delete,Membership,Organization,Self,"resource[""role""] != ""owner""",DELETE,/membership/{id},Worker,Worker \ No newline at end of file diff --git a/cvat/apps/organizations/serializers.py b/cvat/apps/organizations/serializers.py index 70a6a63e718c..906f894e0231 100644 --- a/cvat/apps/organizations/serializers.py +++ b/cvat/apps/organizations/serializers.py @@ -112,6 +112,11 @@ class Meta: fields = ['id', 'user', 'organization', 'is_active', 'joined_date', 'role', 'invitation'] read_only_fields = fields + extra_kwargs = { + 'invitation': { + 'allow_null': True, # owner of an organization does not have an invitation + } + } class MembershipWriteSerializer(serializers.ModelSerializer): def to_representation(self, instance): diff --git a/cvat/schema.yml b/cvat/schema.yml index 19dfcc4b2535..2dc187c5ff63 100644 --- a/cvat/schema.yml +++ b/cvat/schema.yml @@ -7965,6 +7965,7 @@ components: invitation: type: string readOnly: true + nullable: true required: - user MetaUser: diff --git a/tests/python/rest_api/test_memberships.py b/tests/python/rest_api/test_memberships.py index 906fe99dceda..30790e2a79e0 100644 --- a/tests/python/rest_api/test_memberships.py +++ b/tests/python/rest_api/test_memberships.py @@ -4,12 +4,13 @@ # SPDX-License-Identifier: MIT from http import HTTPStatus +from typing import ClassVar, List import pytest from cvat_sdk.api_client.api_client import ApiClient, Endpoint from deepdiff import DeepDiff -from shared.utils.config import get_method, patch_method +from shared.utils.config import get_method, make_api_client, patch_method from .utils import CollectionSimpleFilterTestBase @@ -77,7 +78,8 @@ def test_can_use_simple_filter_for_object_list(self, field): @pytest.mark.usefixtures("restore_db_per_function") class TestPatchMemberships: - _ORG = 2 + _ORG: ClassVar[int] = 1 + ROLES: ClassVar[List[str]] = ["worker", "supervisor", "maintainer", "owner"] def _test_can_change_membership(self, user, membership_id, new_role): response = patch_method( @@ -97,11 +99,16 @@ def _test_cannot_change_membership(self, user, membership_id, new_role): @pytest.mark.parametrize( "who, whom, new_role, is_allow", [ - ("supervisor", "worker", "supervisor", False), - ("supervisor", "maintainer", "supervisor", False), + ("worker", "worker", "supervisor", False), ("worker", "supervisor", "worker", False), ("worker", "maintainer", "worker", False), + ("worker", "owner", "worker", False), + ("supervisor", "worker", "supervisor", False), + ("supervisor", "supervisor", "worker", False), + ("supervisor", "maintainer", "supervisor", False), + ("supervisor", "owner", "worker", False), ("maintainer", "maintainer", "worker", False), + ("maintainer", "owner", "worker", False), ("maintainer", "supervisor", "worker", True), ("maintainer", "worker", "supervisor", True), ("owner", "maintainer", "worker", True), @@ -111,9 +118,85 @@ def _test_cannot_change_membership(self, user, membership_id, new_role): ) def test_user_can_change_role_of_member(self, who, whom, new_role, is_allow, find_users): user = find_users(org=self._ORG, role=who)[0]["username"] - membership_id = find_users(org=self._ORG, role=whom)[1]["membership_id"] + membership_id = find_users(org=self._ORG, role=whom, exclude_username=user)[0][ + "membership_id" + ] if is_allow: self._test_can_change_membership(user, membership_id, new_role) else: self._test_cannot_change_membership(user, membership_id, new_role) + + @pytest.mark.parametrize( + "who", + ROLES, + ) + def test_user_cannot_change_self_role(self, who: str, find_users): + user = find_users(org=self._ORG, role=who)[0] + self._test_cannot_change_membership( + user["username"], user["membership_id"], self.ROLES[abs(self.ROLES.index(who) - 1)] + ) + + +@pytest.mark.usefixtures("restore_db_per_function") +class TestDeleteMemberships: + _ORG: ClassVar[int] = 1 + + def _test_delete_membership( + self, + who: str, + membership_id: int, + is_allow: bool, + ) -> None: + expected_status = HTTPStatus.NO_CONTENT if is_allow else HTTPStatus.FORBIDDEN + + with make_api_client(who) as api_client: + (_, response) = api_client.memberships_api.destroy(membership_id, _check_status=False) + assert response.status == expected_status + + @pytest.mark.parametrize( + "who, is_allow", + [ + ("worker", True), + ("supervisor", True), + ("maintainer", True), + ("owner", False), + ], + ) + def test_member_can_leave_organization(self, who, is_allow, find_users): + user = find_users(role=who, org=self._ORG)[0] + + self._test_delete_membership(user["username"], user["membership_id"], is_allow) + + @pytest.mark.parametrize( + "who, whom, is_allow", + [ + ("worker", "worker", False), + ("worker", "supervisor", False), + ("worker", "maintainer", False), + ("worker", "owner", False), + ("supervisor", "worker", False), + ("supervisor", "supervisor", False), + ("supervisor", "maintainer", False), + ("supervisor", "owner", False), + ("maintainer", "worker", True), + ("maintainer", "supervisor", True), + ("maintainer", "maintainer", False), + ("maintainer", "owner", False), + ("owner", "worker", True), + ("owner", "supervisor", True), + ("owner", "maintainer", True), + ], + ) + def test_member_can_exclude_another_member( + self, + who: str, + whom: str, + is_allow: bool, + find_users, + ): + user = find_users(role=who, org=self._ORG)[0]["username"] + membership_id = find_users(role=whom, org=self._ORG, exclude_username=user)[0][ + "membership_id" + ] + self._test_delete_membership(user, membership_id, is_allow) diff --git a/tests/python/rest_api/test_projects.py b/tests/python/rest_api/test_projects.py index 64614124c6f9..d39ad1d03592 100644 --- a/tests/python/rest_api/test_projects.py +++ b/tests/python/rest_api/test_projects.py @@ -266,14 +266,14 @@ def test_org_supervisor_can_get_project_backup( def test_org_supervisor_cannot_get_project_backup( self, find_users, projects, is_project_staff, is_org_member ): - users = find_users(role="supervisor", exclude_privilege="admin") + users = find_users(exclude_privilege="admin") user, project = next( (user, project) for user, project in product(users, projects) if not is_project_staff(user["id"], project["id"]) and project["organization"] - and is_org_member(user["id"], project["organization"]) + and is_org_member(user["id"], project["organization"], role="supervisor") ) self._test_cannot_get_project_backup(user["username"], project["id"]) @@ -890,14 +890,14 @@ def test_non_project_staff_org_members_cannot_add_label( is_org_member, role, ): - users = find_users(role=role, exclude_privilege="admin") + users = find_users(exclude_privilege="admin") user, project = next( (user, project) for user, project in product(users, projects) if not is_project_staff(user["id"], project["id"]) and project["organization"] - and is_org_member(user["id"], project["organization"]) + and is_org_member(user["id"], project["organization"], role=role) ) new_label = {"name": "new name"} @@ -913,14 +913,14 @@ def test_non_project_staff_org_members_cannot_add_label( def test_project_staff_org_members_can_add_label( self, find_users, projects, is_project_staff, is_org_member, labels, role ): - users = find_users(role=role, exclude_privilege="admin") + users = find_users(exclude_privilege="admin") user, project = next( (user, project) for user, project in product(users, projects) if is_project_staff(user["id"], project["id"]) and project["organization"] - and is_org_member(user["id"], project["organization"]) + and is_org_member(user["id"], project["organization"], role=role) and any(label.get("project_id") == project["id"] for label in labels) ) diff --git a/tests/python/rest_api/test_tasks.py b/tests/python/rest_api/test_tasks.py index 5eaaab11b261..35d842bfc8c0 100644 --- a/tests/python/rest_api/test_tasks.py +++ b/tests/python/rest_api/test_tasks.py @@ -1609,14 +1609,14 @@ def test_non_task_staff_org_members_cannot_add_label( is_org_member, role, ): - users = find_users(role=role, exclude_privilege="admin") + users = find_users(exclude_privilege="admin") user, task = next( (user, task) for user, task in product(users, tasks) if not is_task_staff(user["id"], task["id"]) and task["organization"] - and is_org_member(user["id"], task["organization"]) + and is_org_member(user["id"], task["organization"], role=role) ) new_label = {"name": "new name"} diff --git a/tests/python/shared/assets/cvat_db/cvat_data.tar.bz2 b/tests/python/shared/assets/cvat_db/cvat_data.tar.bz2 index 95ee6febc4ac..18569f25081e 100644 Binary files a/tests/python/shared/assets/cvat_db/cvat_data.tar.bz2 and b/tests/python/shared/assets/cvat_db/cvat_data.tar.bz2 differ diff --git a/tests/python/shared/assets/cvat_db/data.json b/tests/python/shared/assets/cvat_db/data.json index e9653d178cc8..52bb72cd4f6d 100644 --- a/tests/python/shared/assets/cvat_db/data.json +++ b/tests/python/shared/assets/cvat_db/data.json @@ -58,7 +58,7 @@ "pk": 2, "fields": { "password": "md5$phwgPiWB3zBxteEIebm3Yh$27f3556aa8061e5622adfbc8bc305c5c", - "last_login": "2022-09-28T12:15:35.182Z", + "last_login": "2023-09-15T07:52:46.964Z", "is_superuser": false, "username": "user1", "first_name": "User", @@ -693,6 +693,16 @@ "expire_date": "2023-05-15T08:42:48.134Z" } }, +{ + "model": "authtoken.token", + "pk": "0df6f76f61b1fcacb0536c357e10032048f1a4f5", + "fields": { + "user": [ + "user1" + ], + "created": "2023-09-15T07:52:46.956Z" + } +}, { "model": "authtoken.token", "pk": "2dcf8d2ff5032c3cf7d276549af3a50edb4f11c8", @@ -980,6 +990,32 @@ "role": "supervisor" } }, +{ + "model": "organizations.membership", + "pk": 14, + "fields": { + "user": [ + "user4" + ], + "organization": 1, + "is_active": true, + "joined_date": "2023-09-15T07:53:52.115Z", + "role": "supervisor" + } +}, +{ + "model": "organizations.membership", + "pk": 15, + "fields": { + "user": [ + "business2" + ], + "organization": 1, + "is_active": true, + "joined_date": "2023-09-15T07:53:52.116Z", + "role": "maintainer" + } +}, { "model": "organizations.invitation", "pk": "5FjIXya6fTGvlRpauFvi2QN1wDOqo1V9REB5rJinDR8FZO9gr0qmtWpghsCte8Y1", @@ -1068,6 +1104,17 @@ "membership": 4 } }, +{ + "model": "organizations.invitation", + "pk": "d2Zaawf81uImG1nmWA0Va0Bv5EPERt1edJDTgTgMZiefZ2QmC1IdPld9LIPnkiWR", + "fields": { + "created_date": "2023-09-15T07:53:52.115Z", + "owner": [ + "user1" + ], + "membership": 14 + } +}, { "model": "organizations.invitation", "pk": "h43G28di7vfs4Jv5VrKZ26xvGAfm6Yc2FFv14z9EKhiuIEDQ22pEnzmSCab8MnK1", @@ -1101,6 +1148,17 @@ "membership": 8 } }, +{ + "model": "organizations.invitation", + "pk": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX", + "fields": { + "created_date": "2023-09-15T07:53:52.116Z", + "owner": [ + "user1" + ], + "membership": 15 + } +}, { "model": "engine.data", "pk": 2, @@ -10245,6 +10303,184 @@ "organization": 1 } }, +{ + "model": "webhooks.webhookdelivery", + "pk": 1, + "fields": { + "webhook": 6, + "event": "create:invitation", + "status_code": 200, + "redelivery": false, + "created_date": "2023-09-15T07:53:52.457Z", + "updated_date": "2023-09-15T07:53:52.457Z", + "changed_fields": "", + "request": { + "event": "create:invitation", + "sender": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "invitation": { + "key": "d2Zaawf81uImG1nmWA0Va0Bv5EPERt1edJDTgTgMZiefZ2QmC1IdPld9LIPnkiWR", + "role": "supervisor", + "user": { + "id": 5, + "url": "http://localhost:8080/api/users/5", + "username": "user4", + "last_name": "Fourth", + "first_name": "User" + }, + "owner": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "created_date": "2023-09-15T07:53:52.115322Z", + "organization": 1 + }, + "webhook_id": 6 + }, + "response": "\n\n\n Example Domain\n\n \n \n \n \n\n\n\n
\n

Example Domain

\n

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" + } +}, +{ + "model": "webhooks.webhookdelivery", + "pk": 2, + "fields": { + "webhook": 6, + "event": "create:invitation", + "status_code": 200, + "redelivery": false, + "created_date": "2023-09-15T07:53:52.659Z", + "updated_date": "2023-09-15T07:53:52.659Z", + "changed_fields": "", + "request": { + "event": "create:invitation", + "sender": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "invitation": { + "key": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX", + "role": "maintainer", + "user": { + "id": 11, + "url": "http://localhost:8080/api/users/11", + "username": "business2", + "last_name": "Second", + "first_name": "Business" + }, + "owner": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "created_date": "2023-09-15T07:53:52.116135Z", + "organization": 1 + }, + "webhook_id": 6 + }, + "response": "\n\n\n Example Domain\n\n \n \n \n \n\n\n\n
\n

Example Domain

\n

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" + } +}, +{ + "model": "webhooks.webhookdelivery", + "pk": 3, + "fields": { + "webhook": 6, + "event": "update:membership", + "status_code": 200, + "redelivery": false, + "created_date": "2023-09-15T07:53:52.906Z", + "updated_date": "2023-09-15T07:53:52.906Z", + "changed_fields": "is_active,joined_date", + "request": { + "event": "update:membership", + "sender": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "membership": { + "id": 14, + "role": "supervisor", + "user": { + "id": 5, + "url": "http://localhost:8080/api/users/5", + "username": "user4", + "last_name": "Fourth", + "first_name": "User" + }, + "is_active": true, + "invitation": "d2Zaawf81uImG1nmWA0Va0Bv5EPERt1edJDTgTgMZiefZ2QmC1IdPld9LIPnkiWR", + "joined_date": "2023-09-15T07:53:52.115322Z", + "organization": 1 + }, + "webhook_id": 6, + "before_update": { + "is_active": false, + "joined_date": null + } + }, + "response": "\n\n\n Example Domain\n\n \n \n \n \n\n\n\n
\n

Example Domain

\n

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" + } +}, +{ + "model": "webhooks.webhookdelivery", + "pk": 4, + "fields": { + "webhook": 6, + "event": "update:membership", + "status_code": 200, + "redelivery": false, + "created_date": "2023-09-15T07:53:53.135Z", + "updated_date": "2023-09-15T07:53:53.135Z", + "changed_fields": "is_active,joined_date", + "request": { + "event": "update:membership", + "sender": { + "id": 2, + "url": "http://localhost:8080/api/users/2", + "username": "user1", + "last_name": "First", + "first_name": "User" + }, + "membership": { + "id": 15, + "role": "maintainer", + "user": { + "id": 11, + "url": "http://localhost:8080/api/users/11", + "username": "business2", + "last_name": "Second", + "first_name": "Business" + }, + "is_active": true, + "invitation": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX", + "joined_date": "2023-09-15T07:53:52.116135Z", + "organization": 1 + }, + "webhook_id": 6, + "before_update": { + "is_active": false, + "joined_date": null + } + }, + "response": "\n\n\n Example Domain\n\n \n \n \n \n\n\n\n
\n

Example Domain

\n

This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.

\n

More information...

\n
\n\n\n" + } +}, { "model": "quality_control.qualityreport", "pk": 1, diff --git a/tests/python/shared/assets/invitations.json b/tests/python/shared/assets/invitations.json index bcdbb03ce888..5056857e72b0 100644 --- a/tests/python/shared/assets/invitations.json +++ b/tests/python/shared/assets/invitations.json @@ -1,8 +1,48 @@ { - "count": 11, + "count": 13, "next": null, "previous": null, "results": [ + { + "created_date": "2023-09-15T07:53:52.116000Z", + "key": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX", + "organization": 1, + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "role": "maintainer", + "user": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + } + }, + { + "created_date": "2023-09-15T07:53:52.115000Z", + "key": "d2Zaawf81uImG1nmWA0Va0Bv5EPERt1edJDTgTgMZiefZ2QmC1IdPld9LIPnkiWR", + "organization": 1, + "owner": { + "first_name": "User", + "id": 2, + "last_name": "First", + "url": "http://localhost:8080/api/users/2", + "username": "user1" + }, + "role": "supervisor", + "user": { + "first_name": "User", + "id": 5, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/5", + "username": "user4" + } + }, { "created_date": "2022-09-28T13:11:37.839000Z", "key": "hIH9RB3QqZLFwdUDmufaSPc2H8uS5cNjcG6pk8gfAIQ4jg6nJZZWDIQHMN1gFMk9", diff --git a/tests/python/shared/assets/jobs.json b/tests/python/shared/assets/jobs.json index 9520217099f5..539cfae557a1 100644 --- a/tests/python/shared/assets/jobs.json +++ b/tests/python/shared/assets/jobs.json @@ -6,12 +6,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-05-26T16:11:23.946000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 3, + "guide_id": null, "id": 28, "issues": { "count": 0, @@ -37,12 +37,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-05-26T16:11:23.880000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 11, + "guide_id": null, "id": 27, "issues": { "count": 0, @@ -68,12 +68,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-03-27T19:08:07.649000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 4, + "guide_id": null, "id": 26, "issues": { "count": 0, @@ -99,12 +99,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-03-27T19:08:07.649000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 6, + "guide_id": null, "id": 25, "issues": { "count": 0, @@ -130,12 +130,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-10T11:57:31.614000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 2, + "guide_id": null, "id": 24, "issues": { "count": 0, @@ -161,12 +161,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-03-10T11:56:33.757000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 2, + "guide_id": null, "id": 23, "issues": { "count": 0, @@ -192,12 +192,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-01T15:36:26.668000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 2, + "guide_id": null, "id": 22, "issues": { "count": 0, @@ -223,12 +223,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2023-02-10T14:05:25.947000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 21, "issues": { "count": 0, @@ -254,12 +254,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-12-01T12:53:10.425000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 25, + "guide_id": null, "id": 19, "issues": { "count": 0, @@ -285,12 +285,12 @@ { "assignee": null, "bug_tracker": "https://bugtracker.localhost/task/12345", - "guide_id": null, "created_date": "2022-09-22T14:22:25.820000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 8, + "guide_id": null, "id": 18, "issues": { "count": 0, @@ -316,12 +316,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-06-08T08:33:06.505000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 17, "issues": { "count": 0, @@ -353,12 +353,12 @@ "username": "worker2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T10:32:19.149000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 11, + "guide_id": null, "id": 16, "issues": { "count": 1, @@ -384,12 +384,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T09:33:10.420000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 14, "issues": { "count": 0, @@ -415,12 +415,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T09:33:10.420000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 13, "issues": { "count": 0, @@ -446,12 +446,12 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T09:33:10.420000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 12, "issues": { "count": 0, @@ -483,12 +483,12 @@ "username": "worker4" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T09:33:10.420000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 5, + "guide_id": null, "id": 11, "issues": { "count": 1, @@ -520,12 +520,12 @@ "username": "admin1" }, "bug_tracker": null, - "guide_id": null, "created_date": "2022-03-05T08:30:48.612000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 14, + "guide_id": null, "id": 10, "issues": { "count": 1, @@ -551,12 +551,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2022-02-21T10:31:52.429000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 11, + "guide_id": null, "id": 9, "issues": { "count": 1, @@ -582,12 +582,12 @@ { "assignee": null, "bug_tracker": null, - "guide_id": null, "created_date": "2022-02-16T06:26:54.631000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "3d", "frame_count": 1, + "guide_id": null, "id": 8, "issues": { "count": 0, @@ -619,12 +619,12 @@ "username": "worker4" }, "bug_tracker": null, - "guide_id": null, "created_date": "2022-02-16T06:25:48.168000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 25, + "guide_id": null, "id": 7, "issues": { "count": 1, @@ -656,12 +656,12 @@ "username": "worker1" }, "bug_tracker": null, - "guide_id": null, "created_date": "2021-12-14T18:50:29.458000Z", "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "dimension": "2d", "frame_count": 23, + "guide_id": null, "id": 2, "issues": { "count": 0, diff --git a/tests/python/shared/assets/memberships.json b/tests/python/shared/assets/memberships.json index 2c37c8833937..9ae6bcc8d950 100644 --- a/tests/python/shared/assets/memberships.json +++ b/tests/python/shared/assets/memberships.json @@ -1,8 +1,38 @@ { - "count": 13, + "count": 15, "next": null, "previous": null, "results": [ + { + "id": 15, + "invitation": "q8GWTPiR1Vz9DDO6MQo1B6pUBzW9GjDb6AUQPziAV62jD7OpCLZji0GS66C48wRX", + "is_active": true, + "joined_date": "2023-09-15T07:53:52.116000Z", + "organization": 1, + "role": "maintainer", + "user": { + "first_name": "Business", + "id": 11, + "last_name": "Second", + "url": "http://localhost:8080/api/users/11", + "username": "business2" + } + }, + { + "id": 14, + "invitation": "d2Zaawf81uImG1nmWA0Va0Bv5EPERt1edJDTgTgMZiefZ2QmC1IdPld9LIPnkiWR", + "is_active": true, + "joined_date": "2023-09-15T07:53:52.115000Z", + "organization": 1, + "role": "supervisor", + "user": { + "first_name": "User", + "id": 5, + "last_name": "Fourth", + "url": "http://localhost:8080/api/users/5", + "username": "user4" + } + }, { "id": 13, "invitation": "hIH9RB3QqZLFwdUDmufaSPc2H8uS5cNjcG6pk8gfAIQ4jg6nJZZWDIQHMN1gFMk9", diff --git a/tests/python/shared/assets/projects.json b/tests/python/shared/assets/projects.json index f8cc31df8859..560486bf3825 100644 --- a/tests/python/shared/assets/projects.json +++ b/tests/python/shared/assets/projects.json @@ -6,9 +6,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-10T11:58:04.216000Z", "dimension": null, + "guide_id": null, "id": 13, "labels": { "count": 2, @@ -45,9 +45,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-10T11:57:14.944000Z", "dimension": "2d", + "guide_id": null, "id": 12, "labels": { "count": 2, @@ -84,9 +84,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-01T15:36:11.840000Z", "dimension": "2d", + "guide_id": null, "id": 11, "labels": { "count": 1, @@ -129,9 +129,9 @@ "username": "worker3" }, "bug_tracker": "", - "guide_id": null, "created_date": "2023-02-10T11:42:43.192000Z", "dimension": null, + "guide_id": null, "id": 10, "labels": { "count": 2, @@ -168,9 +168,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-12-01T12:52:42.454000Z", "dimension": "2d", + "guide_id": null, "id": 8, "labels": { "count": 2, @@ -213,9 +213,9 @@ "username": "worker4" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-09-28T12:26:25.296000Z", "dimension": null, + "guide_id": null, "id": 7, "labels": { "count": 1, @@ -258,9 +258,9 @@ "username": "business4" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-09-28T12:15:50.768000Z", "dimension": null, + "guide_id": null, "id": 6, "labels": { "count": 1, @@ -297,9 +297,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-09-22T14:21:53.791000Z", "dimension": "2d", + "guide_id": null, "id": 5, "labels": { "count": 2, @@ -336,9 +336,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-06-08T08:32:45.521000Z", "dimension": "2d", + "guide_id": null, "id": 4, "labels": { "count": 2, @@ -373,9 +373,9 @@ "username": "user5" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-28T13:05:24.659000Z", "dimension": null, + "guide_id": null, "id": 3, "labels": { "count": 0, @@ -410,9 +410,9 @@ "username": "user2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2021-12-14T19:52:37.278000Z", "dimension": "2d", + "guide_id": null, "id": 2, "labels": { "count": 2, @@ -457,9 +457,9 @@ "username": "user6" }, "bug_tracker": "", - "guide_id": null, "created_date": "2021-12-14T19:46:37.969000Z", "dimension": "2d", + "guide_id": null, "id": 1, "labels": { "count": 2, diff --git a/tests/python/shared/assets/tasks.json b/tests/python/shared/assets/tasks.json index b6b180efb8f9..0aff2d04349d 100644 --- a/tests/python/shared/assets/tasks.json +++ b/tests/python/shared/assets/tasks.json @@ -6,13 +6,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-05-26T16:11:23.540000Z", "data": 21, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 22, "image_quality": 70, "jobs": { @@ -49,13 +49,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-27T19:08:07.649000Z", "data": 20, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 21, "image_quality": 70, "jobs": { @@ -100,13 +100,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-10T11:57:31.614000Z", "data": 19, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 20, "image_quality": 70, "jobs": { @@ -151,13 +151,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-10T11:56:33.757000Z", "data": 18, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 19, "image_quality": 70, "jobs": { @@ -202,13 +202,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2023-03-01T15:36:26.668000Z", "data": 17, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 18, "image_quality": 70, "jobs": { @@ -259,13 +259,13 @@ "username": "user2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2023-02-10T14:05:25.947000Z", "data": 16, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 17, "image_quality": 70, "jobs": { @@ -302,13 +302,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-12-01T12:53:10.425000Z", "data": 14, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "video", "dimension": "2d", + "guide_id": null, "id": 15, "image_quality": 70, "jobs": { @@ -353,13 +353,13 @@ { "assignee": null, "bug_tracker": "https://bugtracker.localhost/task/12345", - "guide_id": null, "created_date": "2022-09-22T14:22:25.820000Z", "data": 13, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 14, "image_quality": 70, "jobs": { @@ -410,13 +410,13 @@ "username": "user3" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-06-08T08:33:06.505000Z", "data": 12, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 13, "image_quality": 70, "jobs": { @@ -453,9 +453,9 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-14T13:24:05.852000Z", "dimension": "2d", + "guide_id": null, "id": 12, "jobs": { "completed": 0, @@ -496,13 +496,13 @@ "username": "user5" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T10:32:19.149000Z", "data": 11, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 11, "image_quality": 70, "jobs": { @@ -553,13 +553,13 @@ "username": "admin1" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T09:33:10.420000Z", "data": 9, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 9, "image_quality": 70, "jobs": { @@ -602,13 +602,13 @@ "username": "worker4" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-03-05T08:30:48.612000Z", "data": 8, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 8, "image_quality": 70, "jobs": { @@ -651,13 +651,13 @@ "username": "worker2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-02-21T10:31:52.429000Z", "data": 7, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 7, "image_quality": 70, "jobs": { @@ -694,13 +694,13 @@ { "assignee": null, "bug_tracker": "", - "guide_id": null, "created_date": "2022-02-16T06:26:54.631000Z", "data": 6, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "3d", + "guide_id": null, "id": 6, "image_quality": 70, "jobs": { @@ -743,13 +743,13 @@ "username": "dummy2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2022-02-16T06:25:48.168000Z", "data": 5, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "video", "dimension": "2d", + "guide_id": null, "id": 5, "image_quality": 70, "jobs": { @@ -792,13 +792,13 @@ "username": "worker2" }, "bug_tracker": "", - "guide_id": null, "created_date": "2021-12-14T18:50:29.458000Z", "data": 2, "data_chunk_size": 72, "data_compressed_chunk_type": "imageset", "data_original_chunk_type": "imageset", "dimension": "2d", + "guide_id": null, "id": 2, "image_quality": 70, "jobs": { diff --git a/tests/python/shared/assets/users.json b/tests/python/shared/assets/users.json index fc766c32f5c7..99735901fe29 100644 --- a/tests/python/shared/assets/users.json +++ b/tests/python/shared/assets/users.json @@ -310,7 +310,7 @@ "is_active": true, "is_staff": false, "is_superuser": false, - "last_login": "2022-09-28T12:15:35.182000Z", + "last_login": "2023-09-15T07:52:46.964000Z", "last_name": "First", "url": "http://localhost:8080/api/users/2", "username": "user1" diff --git a/tests/python/shared/assets/webhooks.json b/tests/python/shared/assets/webhooks.json index 5b957c0fe66b..da5b0f6837d9 100644 --- a/tests/python/shared/assets/webhooks.json +++ b/tests/python/shared/assets/webhooks.json @@ -30,6 +30,8 @@ ], "id": 6, "is_active": true, + "last_delivery_date": "2023-09-15T07:53:53.135000Z", + "last_status": 200, "organization": 1, "owner": { "first_name": "Admin",