diff --git a/.github/workflows/backend-linters.yaml b/.github/workflows/backend-linters.yaml index d8bb35d69..31d0a1dd4 100644 --- a/.github/workflows/backend-linters.yaml +++ b/.github/workflows/backend-linters.yaml @@ -30,6 +30,10 @@ jobs: working-directory: ${{env.working-directory}} run: | python -m pip install ruff - - name: ruff + - name: Run ruff format check working-directory: ${{env.working-directory}} - run: ruff check . + run: ruff format --check . + # NOTE: The following will be uncommented once the codebase is cleaned up + # - name: ruff + # working-directory: ${{env.working-directory}} + # run: ruff check . diff --git a/.github/workflows/frontend-linters.yaml b/.github/workflows/frontend-linters.yaml index 69adc89c9..a7128dda3 100644 --- a/.github/workflows/frontend-linters.yaml +++ b/.github/workflows/frontend-linters.yaml @@ -26,18 +26,25 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - name: Install latest npm + - name: Install prettier working-directory: ${{env.working-directory}} - run: | - npm install -g npm && - npm --version && - npm list -g --depth 0 - - name: Install dependencies + run: npm install --save-dev prettier + - name: Run prettier working-directory: ${{env.working-directory}} - run: npm ci - - name: Run prettier check & eslint - working-directory: ${{env.working-directory}} - run: npm run lint - - name: Run svelte-check - working-directory: ${{env.working-directory}} - run: npm run check + run: npx prettier --check . + # NOTE: The following will be uncommented once the codebase is cleaned up + # - name: Install latest npm + # working-directory: ${{env.working-directory}} + # run: | + # npm install -g npm && + # npm --version && + # npm list -g --depth 0 + # - name: Install dependencies + # working-directory: ${{env.working-directory}} + # run: npm ci + # - name: Run prettier check & eslint + # working-directory: ${{env.working-directory}} + # run: npm run lint + # - name: Run svelte-check + # working-directory: ${{env.working-directory}} + # run: npm run check diff --git a/backend/app_tests/api/test_api_compliance_assessments.py b/backend/app_tests/api/test_api_compliance_assessments.py index 0ff2003d7..14dc7529c 100644 --- a/backend/app_tests/api/test_api_compliance_assessments.py +++ b/backend/app_tests/api/test_api_compliance_assessments.py @@ -118,7 +118,10 @@ def test_get_compliance_assessments(self, test): "framework": Framework.objects.all()[0], }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "framework": { "id": str(Framework.objects.all()[0].id), "str": str(Framework.objects.all()[0]), @@ -146,7 +149,10 @@ def test_create_compliance_assessments(self, test): "framework": str(Framework.objects.all()[0].id), }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "framework": { "id": str(Framework.objects.all()[0].id), "str": str(Framework.objects.all()[0]), @@ -186,7 +192,10 @@ def test_update_compliance_assessments(self, test): "framework": str(Framework.objects.all()[1].id), }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "framework": { "id": str(Framework.objects.all()[0].id), "str": str(Framework.objects.all()[0]), diff --git a/backend/app_tests/api/test_api_libraries.py b/backend/app_tests/api/test_api_libraries.py index 9408b43c3..f6ec9846c 100644 --- a/backend/app_tests/api/test_api_libraries.py +++ b/backend/app_tests/api/test_api_libraries.py @@ -67,19 +67,23 @@ def test_import_frameworks(self, test): Framework.objects.all().count() == 0 ), "libraries are already imported in the database" EndpointTestsQueries.Auth.get_object( - test.client, "Frameworks", user_group=test.user_group, + test.client, + "Frameworks", + user_group=test.user_group, ) EndpointTestsQueries.Auth.import_object( test.client, "Framework", user_group=test.user_group ) - assert ( - Framework.objects.all().count() == (1 if not EndpointTestsUtils.expected_request_response( - "add", "library", str(test.folder), test.user_group - )[0] else 0) + assert Framework.objects.all().count() == ( + 1 + if not EndpointTestsUtils.expected_request_response( + "add", "library", str(test.folder), test.user_group + )[0] + else 0 ), "Frameworks are not correctly imported in the database" - + # Uses the API endpoint to assert that the library was properly imported EndpointTestsQueries.Auth.get_object( test.client, @@ -93,8 +97,8 @@ def test_import_frameworks(self, test): base_count=1, user_group=test.user_group, fails=EndpointTestsUtils.expected_request_response( - "add", "library", str(test.folder), test.user_group - )[0] + "add", "library", str(test.folder), test.user_group + )[0], ) def test_delete_frameworks(self, test): @@ -133,10 +137,12 @@ def test_import_risk_matrix(self, test): test.client, "Risk matrix", user_group=test.user_group ) - assert ( - RiskMatrix.objects.all().count() == (1 if not EndpointTestsUtils.expected_request_response( - "add", "library", str(test.folder), test.user_group - )[0] else 0) + assert RiskMatrix.objects.all().count() == ( + 1 + if not EndpointTestsUtils.expected_request_response( + "add", "library", str(test.folder), test.user_group + )[0] + else 0 ), "Risk matrices are not correctly imported in the database" # Uses the API endpoint to assert that the library was properly imported @@ -153,8 +159,8 @@ def test_import_risk_matrix(self, test): base_count=1, user_group=test.user_group, fails=EndpointTestsUtils.expected_request_response( - "add", "library", str(test.folder), test.user_group - )[0] + "add", "library", str(test.folder), test.user_group + )[0], ) def test_delete_matrix(self, test): @@ -163,14 +169,13 @@ def test_delete_matrix(self, test): EndpointTestsQueries.Auth.import_object(test.admin_client, "Risk matrix") EndpointTestsQueries.Auth.delete_object( test.client, - "Risk matrices", - RiskMatrix, + "Risk matrices", + RiskMatrix, user_group=test.user_group, scope="Global", - **({ - "fails": True, - "expected_status": status.HTTP_403_FORBIDDEN - } - if test.user_group == "BI-UG-DMA" else {} # Domain Manager can't delete Global risk matrices (i.e. imported matrices) - ) + **( + {"fails": True, "expected_status": status.HTTP_403_FORBIDDEN} + if test.user_group == "BI-UG-DMA" + else {} # Domain Manager can't delete Global risk matrices (i.e. imported matrices) + ), ) diff --git a/backend/app_tests/api/test_api_projects.py b/backend/app_tests/api/test_api_projects.py index 5865602e8..2172b14d6 100644 --- a/backend/app_tests/api/test_api_projects.py +++ b/backend/app_tests/api/test_api_projects.py @@ -99,7 +99,7 @@ def test_get_projects(self, test): "lc_status": PROJECT_STATUS[1], }, user_group=test.user_group, - scope=str(test.folder) + scope=str(test.folder), ) def test_create_projects(self, test): @@ -121,7 +121,7 @@ def test_create_projects(self, test): "lc_status": PROJECT_STATUS[1], }, user_group=test.user_group, - scope=str(test.folder) + scope=str(test.folder), ) def test_update_projects(self, test): diff --git a/backend/app_tests/api/test_api_risk_assessments.py b/backend/app_tests/api/test_api_risk_assessments.py index 22ad2431a..3f4e4d7fa 100644 --- a/backend/app_tests/api/test_api_risk_assessments.py +++ b/backend/app_tests/api/test_api_risk_assessments.py @@ -121,7 +121,10 @@ def test_get_risk_assessments(self, test): "risk_matrix": risk_matrix, }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, user_group=test.user_group, @@ -147,7 +150,10 @@ def test_create_risk_assessments(self, test): "risk_matrix": str(risk_matrix.id), }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, user_group=test.user_group, @@ -185,7 +191,10 @@ def test_update_risk_assessments(self, test): "risk_matrix": str(risk_matrix2.id), }, { - "project": {"id": str(project.id), "str": project.folder.name + "/" + project.name}, + "project": { + "id": str(project.id), + "str": project.folder.name + "/" + project.name, + }, "risk_matrix": {"id": str(risk_matrix.id), "str": str(risk_matrix)}, }, user_group=test.user_group, diff --git a/backend/app_tests/api/test_api_users.py b/backend/app_tests/api/test_api_users.py index c19ddc6d0..a20d13175 100644 --- a/backend/app_tests/api/test_api_users.py +++ b/backend/app_tests/api/test_api_users.py @@ -86,7 +86,7 @@ def test_get_users(self, test): base_count=2, item_search_field="email", user_group=test.user_group, - scope="Global" + scope="Global", ) def test_create_users(self, test): @@ -100,7 +100,7 @@ def test_create_users(self, test): base_count=2, item_search_field="email", user_group=test.user_group, - scope="Global" + scope="Global", ) def test_update_users(self, test): @@ -117,7 +117,7 @@ def test_update_users(self, test): "last_name": "new" + USER_NAME, }, user_group=test.user_group, - scope="Global" + scope="Global", ) def test_delete_users(self, test): @@ -129,7 +129,7 @@ def test_delete_users(self, test): User, {"email": USER_EMAIL, "first_name": USER_FIRSTNAME, "last_name": USER_NAME}, user_group=test.user_group, - scope="Global" + scope="Global", ) def test_uniqueness_emails(self, test): diff --git a/backend/core/apps.py b/backend/core/apps.py index 9d49751c9..5106d7134 100644 --- a/backend/core/apps.py +++ b/backend/core/apps.py @@ -251,9 +251,7 @@ def startup(**kwargs): print("startup handler: initialize database") - reader_permissions = Permission.objects.filter( - codename__in=READER_PERMISSIONS_LIST - ) + reader_permissions = Permission.objects.filter(codename__in=READER_PERMISSIONS_LIST) approver_permissions = Permission.objects.filter( codename__in=APPROVER_PERMISSIONS_LIST diff --git a/backend/core/base_models.py b/backend/core/base_models.py index 4f11da487..bb978fd70 100644 --- a/backend/core/base_models.py +++ b/backend/core/base_models.py @@ -92,7 +92,9 @@ def clean(self) -> None: if not self.is_unique_in_scope(scope=scope, fields_to_check=_fields_to_check): for field in _fields_to_check: if not self.is_unique_in_scope(scope=scope, fields_to_check=[field]): - field_errors[field] = f"{getattr(self, field)} is already used in this scope. Please choose another value." + field_errors[ + field + ] = f"{getattr(self, field)} is already used in this scope. Please choose another value." super().clean() if field_errors: raise ValidationError(field_errors) diff --git a/backend/core/helpers.py b/backend/core/helpers.py index 34a63f808..1f238efa1 100644 --- a/backend/core/helpers.py +++ b/backend/core/helpers.py @@ -230,7 +230,11 @@ def get_sorted_requirement_nodes_rec( for requirement_node in requirement_nodes if requirement_node.parent_urn == node.urn ] - req_as = requirement_assessment_from_requirement_id[str(node.id)] if requirements_assessed else None + req_as = ( + requirement_assessment_from_requirement_id[str(node.id)] + if requirements_assessed + else None + ) result[str(node.id)] = { "urn": node.urn, "parent_urn": node.parent_urn, @@ -238,8 +242,12 @@ def get_sorted_requirement_nodes_rec( "name": node.name, "ra_id": str(req_as.id) if requirements_assessed else None, "status": req_as.status if requirements_assessed else None, - "status_display": req_as.get_status_display() if requirements_assessed else None, - "status_i18n": camel_case(req_as.status) if requirements_assessed else None, + "status_display": req_as.get_status_display() + if requirements_assessed + else None, + "status_i18n": camel_case(req_as.status) + if requirements_assessed + else None, "node_content": node.display_long, "style": "node", "assessable": node.assessable, @@ -494,7 +502,7 @@ def assessment_per_status(user: User, model: RiskAssessment | ComplianceAssessme v = {"value": count, "itemStyle": {"color": color_map[st[0]]}} values.append(v) labels.append(st[1]) - #add undefined as the first element in the labels to balance the values + # add undefined as the first element in the labels to balance the values labels.insert(0, "undefined") local_lables = [camel_case(str(label)) for label in labels] return {"localLables": local_lables, "labels": labels, "values": values} diff --git a/backend/core/migrations/0007_alter_requirementlevel_framework_and_more.py b/backend/core/migrations/0007_alter_requirementlevel_framework_and_more.py index beb35d513..cd15ea776 100644 --- a/backend/core/migrations/0007_alter_requirementlevel_framework_and_more.py +++ b/backend/core/migrations/0007_alter_requirementlevel_framework_and_more.py @@ -5,20 +5,33 @@ class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_remove_securitymeasure_security_function_and_more'), + ("core", "0006_remove_securitymeasure_security_function_and_more"), ] operations = [ migrations.AlterField( - model_name='requirementlevel', - name='framework', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='requirement_levels', to='core.framework', verbose_name='Framework'), + model_name="requirementlevel", + name="framework", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="requirement_levels", + to="core.framework", + verbose_name="Framework", + ), ), migrations.AlterField( - model_name='requirementnode', - name='framework', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='requirement_nodes', to='core.framework', verbose_name='Framework'), + model_name="requirementnode", + name="framework", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="requirement_nodes", + to="core.framework", + verbose_name="Framework", + ), ), ] diff --git a/backend/core/migrations/0008_alter_complianceassessment_status_and_more.py b/backend/core/migrations/0008_alter_complianceassessment_status_and_more.py index 7d089b187..cb4557e30 100644 --- a/backend/core/migrations/0008_alter_complianceassessment_status_and_more.py +++ b/backend/core/migrations/0008_alter_complianceassessment_status_and_more.py @@ -4,20 +4,45 @@ class Migration(migrations.Migration): - dependencies = [ - ('core', '0007_alter_requirementlevel_framework_and_more'), + ("core", "0007_alter_requirementlevel_framework_and_more"), ] operations = [ migrations.AlterField( - model_name='complianceassessment', - name='status', - field=models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status'), + model_name="complianceassessment", + name="status", + field=models.CharField( + blank=True, + choices=[ + ("planned", "Planned"), + ("in_progress", "In progress"), + ("in_review", "In review"), + ("done", "Done"), + ("deprecated", "Deprecated"), + ], + default="planned", + max_length=100, + null=True, + verbose_name="Status", + ), ), migrations.AlterField( - model_name='riskassessment', - name='status', - field=models.CharField(blank=True, choices=[('planned', 'Planned'), ('in_progress', 'In progress'), ('in_review', 'In review'), ('done', 'Done'), ('deprecated', 'Deprecated')], default='planned', max_length=100, null=True, verbose_name='Status'), + model_name="riskassessment", + name="status", + field=models.CharField( + blank=True, + choices=[ + ("planned", "Planned"), + ("in_progress", "In progress"), + ("in_review", "In review"), + ("done", "Done"), + ("deprecated", "Deprecated"), + ], + default="planned", + max_length=100, + null=True, + verbose_name="Status", + ), ), ] diff --git a/backend/core/models.py b/backend/core/models.py index 04762a1ad..66a1a0fab 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -487,7 +487,7 @@ def overall_compliance(self): return round(count * 100 / total) def __str__(self): - return self.folder.name + '/' + self.name + return self.folder.name + "/" + self.name class Asset(NameDescriptionMixin, FolderMixin, PublishInRootFolderMixin): @@ -1236,11 +1236,7 @@ def get_strength_of_knowledge(self): return self.DEFAULT_SOK_OPTIONS[self.strength_of_knowledge] def __str__(self): - return ( - str(self.parent_project()) - + _(": ") - + str(self.name) - ) + return str(self.parent_project()) + _(": ") + str(self.name) @property def rid(self): diff --git a/backend/core/permissions.py b/backend/core/permissions.py index f6c38b4ee..05f8bb682 100644 --- a/backend/core/permissions.py +++ b/backend/core/permissions.py @@ -9,7 +9,8 @@ class RBACPermissions(permissions.DjangoObjectPermissions): - """ this is the DRF custom permission model enforcing our RBAC logic """ + """this is the DRF custom permission model enforcing our RBAC logic""" + perms_map = { "GET": ["%(app_label)s.view_%(model_name)s"], "OPTIONS": [], diff --git a/backend/core/views.py b/backend/core/views.py index da1613d8b..b89f111a0 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -837,24 +837,31 @@ def get_queryset(self): def update(self, request: Request, *args, **kwargs) -> Response: user = self.get_object() - if user.is_admin() : + if user.is_admin(): number_of_admin_users = User.get_admin_users().count() admin_group = UserGroup.objects.get(name="BI-UG-ADM") - if number_of_admin_users == 1 : + if number_of_admin_users == 1: new_user_groups = set(request.data["user_groups"]) - if str(admin_group.pk) not in new_user_groups : - return Response({"error":"attemptToRemoveOnlyAdminUserGroup"},status=HTTP_403_FORBIDDEN) + if str(admin_group.pk) not in new_user_groups: + return Response( + {"error": "attemptToRemoveOnlyAdminUserGroup"}, + status=HTTP_403_FORBIDDEN, + ) return super().update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): user = self.get_object() - if user.is_admin() : + if user.is_admin(): number_of_admin_users = User.get_admin_users().count() - if number_of_admin_users == 1 : - return Response({"error":"attemptToDeleteOnlyAdminAccountError"},status=HTTP_403_FORBIDDEN) + if number_of_admin_users == 1: + return Response( + {"error": "attemptToDeleteOnlyAdminAccountError"}, + status=HTTP_403_FORBIDDEN, + ) + + return super().destroy(request, *args, **kwargs) - return super().destroy(request,*args,**kwargs) class UserGroupViewSet(BaseModelViewSet): """ diff --git a/backend/iam/migrations/0002_purge_validator.py b/backend/iam/migrations/0002_purge_validator.py index 3778b690b..00c772228 100644 --- a/backend/iam/migrations/0002_purge_validator.py +++ b/backend/iam/migrations/0002_purge_validator.py @@ -2,27 +2,27 @@ from django.db import migrations + def rename_validator(apps, schema_editor): """ rename the validator role since it has been replaced by approver. """ UserGroup = apps.get_model("iam", "UserGroup") for ug in UserGroup.objects.filter(name="BI-UG-GVA"): - ug.name="BI-UG-GAP" + ug.name = "BI-UG-GAP" ug.save() for ug in UserGroup.objects.filter(name="BI-UG-VAL"): - ug.name="BI-UG-APP" + ug.name = "BI-UG-APP" ug.save() Role = apps.get_model("iam", "Role") for role in Role.objects.filter(name="BI-RL-VAL"): - role.name="BI-RL-APP" + role.name = "BI-RL-APP" role.save() class Migration(migrations.Migration): - dependencies = [ - ('iam', '0001_initial'), + ("iam", "0001_initial"), ] operations = [ diff --git a/backend/iam/models.py b/backend/iam/models.py index 5cf7c5b0d..1a701ac28 100644 --- a/backend/iam/models.py +++ b/backend/iam/models.py @@ -236,6 +236,7 @@ def get_user_groups(user): user_group_list.append(user_group) return user_group_list + class UserManager(BaseUserManager): use_in_migrations = True @@ -290,6 +291,7 @@ def create_superuser(self, email, password=None, **extra_fields): UserGroup.objects.get(name="BI-UG-ADM").user_set.add(superuser) return superuser + class User(AbstractBaseUser, AbstractBaseModel, FolderMixin): """a user is a principal corresponding to a human""" @@ -470,12 +472,13 @@ def set_username(self, username): self.email = username @staticmethod - def get_admin_users() -> List[Self] : + def get_admin_users() -> List[Self]: return User.objects.filter(user_groups__name="BI-UG-ADM") - def is_admin(self) -> bool : + def is_admin(self) -> bool: return self.user_groups.filter(name="BI-UG-ADM").exists() + class Role(NameDescriptionMixin, FolderMixin): """A role is a list of permissions""" diff --git a/backend/library/utils.py b/backend/library/utils.py index b06b74878..34d622514 100644 --- a/backend/library/utils.py +++ b/backend/library/utils.py @@ -54,8 +54,10 @@ def get_available_library_files(): files.append(f) return files + AVAILABLE_LIBRARIES = {} + def get_available_libraries(): """ Returns a list of available libraries @@ -69,12 +71,12 @@ def get_available_libraries(): for f in files: fname = path / f modified_time = os.path.getmtime(fname) - libs = AVAILABLE_LIBRARIES.get((fname,modified_time)) - if libs is None : + libs = AVAILABLE_LIBRARIES.get((fname, modified_time)) + if libs is None: with open(fname, "r", encoding="utf-8") as file: libs = list(yaml.safe_load_all(file)) - AVAILABLE_LIBRARIES[(fname,os.path.getmtime(fname))] = libs - for _lib in libs : + AVAILABLE_LIBRARIES[(fname, os.path.getmtime(fname))] = libs + for _lib in libs: if (lib := Library.objects.filter(urn=_lib["urn"]).first()) is not None: _lib["id"] = lib.id _lib["reference_count"] = lib.reference_count @@ -213,6 +215,7 @@ def import_requirement_node(self, framework_object: Framework): ReferenceControl.objects.get(urn=reference_control.lower()) ) + # The couple (URN, locale) is unique. ===> Check it in the future class FrameworkImporter: REQUIRED_FIELDS = {"ref_id", "urn"} @@ -586,13 +589,11 @@ def import_objects(self, library_object): risk_matrix.import_risk_matrix(library_object) @transaction.atomic - def _import_library(self) : + def _import_library(self): library_object = self.create_or_update_library() self.import_objects(library_object) library_object.dependencies.set( - Library.objects.filter( - urn__in=self._library_data.get("dependencies", []) - ) + Library.objects.filter(urn__in=self._library_data.get("dependencies", [])) ) def import_library(self): @@ -602,20 +603,21 @@ def import_library(self): self.check_and_import_dependencies() - for _ in range(10) : + for _ in range(10): try: self._import_library() break - except OperationalError as e : - if e.args and e.args[0] == 'database is locked' : + except OperationalError as e: + if e.args and e.args[0] == "database is locked": time.sleep(1) - else : + else: raise e - except Exception as e : + except Exception as e: # TODO: Switch to proper logging print(f"Library import exception: {e}") raise e + def import_library_view(library: dict) -> Union[str, None]: """ Imports a library diff --git a/backend/library/views.py b/backend/library/views.py index f570e267a..b1867a526 100644 --- a/backend/library/views.py +++ b/backend/library/views.py @@ -31,6 +31,7 @@ from .serializers import LibrarySerializer, LibraryUploadSerializer from .utils import get_available_libraries, get_library, import_library_view + class LibraryViewSet(BaseModelViewSet): serializer_class = LibrarySerializer parser_classes = [FileUploadParser] @@ -67,7 +68,7 @@ def destroy(self, request, *args, pk, **kwargs): return Response(data="Library not found.", status=status.HTTP_404_NOT_FOUND) # "reference_count" is not always defined (is this normal ?) - if library.get("reference_count",0) != 0 : + if library.get("reference_count", 0) != 0: return Response( data="Library cannot be deleted because it has references.", status=status.HTTP_400_BAD_REQUEST, @@ -153,9 +154,11 @@ def upload_library(self, request): return HttpResponse(json.dumps({}), status=HTTP_200_OK) except IntegrityError: return HttpResponse( - json.dumps({"error" : "libraryAlreadyImportedError"}), status=HTTP_400_BAD_REQUEST + json.dumps({"error": "libraryAlreadyImportedError"}), + status=HTTP_400_BAD_REQUEST, ) - except : + except: return HttpResponse( - json.dumps({"error": "invalidLibraryFileError"}), status=HTTP_400_BAD_REQUEST + json.dumps({"error": "invalidLibraryFileError"}), + status=HTTP_400_BAD_REQUEST, ) diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 7e1bc2634..5a12e9941 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -1,12 +1,12 @@ module.exports = { root: true, extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:svelte/recommended", - "prettier", - "plugin:storybook/recommended" - ], + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:svelte/recommended', + 'prettier', + 'plugin:storybook/recommended' + ], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], parserOptions: { diff --git a/frontend/messages/ar.json b/frontend/messages/ar.json index d74701b5e..0a67ba60c 100644 --- a/frontend/messages/ar.json +++ b/frontend/messages/ar.json @@ -2,4 +2,3 @@ "$schema": "https://inlang.com/schema/inlang-message-format", "type": "نوع" } - diff --git a/frontend/package.json b/frontend/package.json index 8ff4722ad..6c3722dc1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,83 +1,83 @@ { - "name": "frontend", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "paraglide-js compile --project ./project.inlang && vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test": "vitest", - "test:ci": "vitest run", - "test:ui": "vitest --ui", - "test:e2e": "ARGS=\"$npm_config_args\" docker compose -f ./tests/docker-compose.e2e-tests.yml up --force-recreate --build -V", - "coverage": "vitest run --coverage", - "lint": "prettier --plugin-search-dir . --check . && eslint .", - "format": "prettier --plugin-search-dir . --write .", - "postinstall": "paraglide-js compile --project ./project.inlang", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" - }, - "devDependencies": { - "@inlang/paraglide-js": "1.2.5", - "@playwright/test": "^1.40.1", - "@skeletonlabs/skeleton": "^2.3.0", - "@skeletonlabs/tw-plugin": "^0.2.2", - "@storybook/addon-essentials": "^7.6.17", - "@storybook/addon-interactions": "^7.6.17", - "@storybook/addon-links": "^7.6.17", - "@storybook/blocks": "^7.6.17", - "@storybook/svelte": "^7.6.17", - "@storybook/sveltekit": "^7.6.17", - "@storybook/test": "^7.6.17", - "@sveltejs/adapter-auto": "^3.0.0", - "@sveltejs/adapter-node": "^4.0.1", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0", - "@tailwindcss/forms": "^0.5.3", - "@tailwindcss/typography": "^0.5.9", - "@testing-library/jest-dom": "^6.1.4", - "@testing-library/svelte": "^4.0.4", - "@types/node": "^20.8.7", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", - "@vincjo/datatables": "^1.14.0", - "@vitest/coverage-v8": "^1.1.1", - "@vitest/ui": "^1.1.1", - "autoprefixer": "^10.4.14", - "eslint": "^8.53.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-storybook": "^0.8.0", - "eslint-plugin-svelte": "^2.35.1", - "jsdom": "^22.1.0", - "postcss": "^8.4.23", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "storybook": "^7.6.17", - "svelte": "^4.0.0", - "svelte-check": "^3.4.3", - "svelte-typewriter": "^3.2.3", - "sveltekit-flash-message": "^2.2.1", - "sveltekit-rate-limiter": "^0.4.1", - "sveltekit-superforms": "^1.6.0", - "tailwindcss": "^3.3.2", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.0.0", - "vite-plugin-tailwind-purgecss": "^0.2.0", - "vitest": "^1.1.1", - "zod": "^3.22.2" - }, - "type": "module", - "dependencies": { - "@floating-ui/dom": "^1.5.1", - "@fortawesome/fontawesome-free": "^6.5.1", - "@inlang/paraglide-js-adapter-vite": "^1.2.14", - "dotenv": "^16.4.1", - "echarts": "^5.4.3", - "svelte-multiselect": "^10.2.0" - } + "name": "frontend", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "paraglide-js compile --project ./project.inlang && vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "vitest", + "test:ci": "vitest run", + "test:ui": "vitest --ui", + "test:e2e": "ARGS=\"$npm_config_args\" docker compose -f ./tests/docker-compose.e2e-tests.yml up --force-recreate --build -V", + "coverage": "vitest run --coverage", + "lint": "prettier --plugin-search-dir . --check . && eslint .", + "format": "prettier --plugin-search-dir . --write .", + "postinstall": "paraglide-js compile --project ./project.inlang", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "devDependencies": { + "@inlang/paraglide-js": "1.2.5", + "@playwright/test": "^1.40.1", + "@skeletonlabs/skeleton": "^2.3.0", + "@skeletonlabs/tw-plugin": "^0.2.2", + "@storybook/addon-essentials": "^7.6.17", + "@storybook/addon-interactions": "^7.6.17", + "@storybook/addon-links": "^7.6.17", + "@storybook/blocks": "^7.6.17", + "@storybook/svelte": "^7.6.17", + "@storybook/sveltekit": "^7.6.17", + "@storybook/test": "^7.6.17", + "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/adapter-node": "^4.0.1", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@tailwindcss/forms": "^0.5.3", + "@tailwindcss/typography": "^0.5.9", + "@testing-library/jest-dom": "^6.1.4", + "@testing-library/svelte": "^4.0.4", + "@types/node": "^20.8.7", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "@vincjo/datatables": "^1.14.0", + "@vitest/coverage-v8": "^1.1.1", + "@vitest/ui": "^1.1.1", + "autoprefixer": "^10.4.14", + "eslint": "^8.53.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-svelte": "^2.35.1", + "jsdom": "^22.1.0", + "postcss": "^8.4.23", + "prettier": "^2.8.0", + "prettier-plugin-svelte": "^2.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "storybook": "^7.6.17", + "svelte": "^4.0.0", + "svelte-check": "^3.4.3", + "svelte-typewriter": "^3.2.3", + "sveltekit-flash-message": "^2.2.1", + "sveltekit-rate-limiter": "^0.4.1", + "sveltekit-superforms": "^1.6.0", + "tailwindcss": "^3.3.2", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^5.0.0", + "vite-plugin-tailwind-purgecss": "^0.2.0", + "vitest": "^1.1.1", + "zod": "^3.22.2" + }, + "type": "module", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@fortawesome/fontawesome-free": "^6.5.1", + "@inlang/paraglide-js-adapter-vite": "^1.2.14", + "dotenv": "^16.4.1", + "echarts": "^5.4.3", + "svelte-multiselect": "^10.2.0" + } } diff --git a/frontend/project.inlang/settings.json b/frontend/project.inlang/settings.json index 66d4dcafc..d4cf30191 100644 --- a/frontend/project.inlang/settings.json +++ b/frontend/project.inlang/settings.json @@ -1,21 +1,17 @@ { - "$schema": "https://inlang.com/schema/project-settings", - "sourceLanguageTag": "en", - "languageTags": [ - "en", - "ar", - "fr" - ], - "modules": [ - "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js", - "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js" - ], - "plugin.inlang.messageFormat": { - "pathPattern": "./messages/{languageTag}.json" - } -} \ No newline at end of file + "$schema": "https://inlang.com/schema/project-settings", + "sourceLanguageTag": "en", + "languageTags": ["en", "ar", "fr"], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{languageTag}.json" + } +} diff --git a/frontend/server/index.js b/frontend/server/index.js index 6b0ff570a..3a99edff6 100644 --- a/frontend/server/index.js +++ b/frontend/server/index.js @@ -1,21 +1,21 @@ import { server } from '../build/index.js'; process.on('SIGINT', () => { - console.log('Got SIGINT. Starting graceful shutdown.'); - shutdownServer(); + console.log('Got SIGINT. Starting graceful shutdown.'); + shutdownServer(); }); process.on('SIGTERM', () => { - console.log('Got SIGTERM. Starting graceful shutdown.'); - shutdownServer(); + console.log('Got SIGTERM. Starting graceful shutdown.'); + shutdownServer(); }); function shutdownServer() { - server.server?.close(() => { - console.log('Server closed'); - process.exit(0); - }); - server.server?.closeIdleConnections(); - setInterval(() => server.server?.closeIdleConnections(), 1_000); - setTimeout(() => server.server?.closeAllConnections(), 20_000); -} \ No newline at end of file + server.server?.close(() => { + console.log('Server closed'); + process.exit(0); + }); + server.server?.closeIdleConnections(); + setInterval(() => server.server?.closeIdleConnections(), 1_000); + setTimeout(() => server.server?.closeAllConnections(), 20_000); +} diff --git a/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte b/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte index 7a537f57b..277b3dd9c 100644 --- a/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte +++ b/frontend/src/lib/components/Breadcrumbs/Breadcrumbs.svelte @@ -38,14 +38,17 @@ } } else if (t === 'folders') { t = 'domains'; - } - else{ + } else { t = t.replace(/-/g, ' '); t = capitalizeSecondWord(t); } return { label: $page.data.label || t, - href: Object.keys(listViewFields).includes(tokens[0]) && !listViewFields[tokens[0]].breadcrumb_link_disabled ? tokenPath : null + href: + Object.keys(listViewFields).includes(tokens[0]) && + !listViewFields[tokens[0]].breadcrumb_link_disabled + ? tokenPath + : null }; }); diff --git a/frontend/src/lib/components/Forms/AutocompleteSelect.svelte b/frontend/src/lib/components/Forms/AutocompleteSelect.svelte index 8c100fb09..851ba3470 100644 --- a/frontend/src/lib/components/Forms/AutocompleteSelect.svelte +++ b/frontend/src/lib/components/Forms/AutocompleteSelect.svelte @@ -30,7 +30,8 @@ const default_value = nullable ? null : selectedValues[0]; - $: ($value = multiple ? selectedValues : selectedValues[0] ?? default_value), handleSelectChange(); + $: ($value = multiple ? selectedValues : selectedValues[0] ?? default_value), + handleSelectChange(); $: disabled = selected.length && options.length === 1 && $constraints?.required; @@ -87,12 +88,10 @@ {#if option.suggested} {option.label} (suggested) + {:else if localItems(languageTag())[toCamelCase(option.label)]} + {localItems(languageTag())[toCamelCase(option.label)]} {:else} - {#if localItems(languageTag())[toCamelCase(option.label)]} - {localItems(languageTag())[toCamelCase(option.label)]} - {:else} - {option.label} - {/if} + {option.label} {/if} {:else} diff --git a/frontend/src/lib/components/Forms/ModelForm.svelte b/frontend/src/lib/components/Forms/ModelForm.svelte index bda9e89bd..eaa9aa684 100644 --- a/frontend/src/lib/components/Forms/ModelForm.svelte +++ b/frontend/src/lib/components/Forms/ModelForm.svelte @@ -44,7 +44,9 @@ onMount(() => { if (shape.reference_control) { - const reference_control_input: HTMLElement | null = document.querySelector(`div.multiselect[role="searchbox"] input`); // The MultiSelect component can't be focused automatically with data-focusindex="0" so we focus manually + const reference_control_input: HTMLElement | null = document.querySelector( + `div.multiselect[role="searchbox"] input` + ); // The MultiSelect component can't be focused automatically with data-focusindex="0" so we focus manually reference_control_input?.focus(); } }); @@ -69,7 +71,7 @@ {form} options={getOptions({ objects: model.foreignKeys['reference_control'], - extra_fields: [["folder","str"]], + extra_fields: [['folder', 'str']], suggestions: suggestions['reference_control'] })} field="reference_control" @@ -97,10 +99,10 @@ /> {/if} {#if shape.name} - + {/if} {#if shape.description} -