From babe21cf8d552d638c9ebcc340dc7c905f515faa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 20:44:09 +0000 Subject: [PATCH 01/40] Bump cryptography from 41.0.1 to 41.0.4 in /backend Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.1 to 41.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/41.0.1...41.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index c2916e42..79ade505 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,7 +3,7 @@ certifi==2023.7.22 cffi==1.15.1 charset-normalizer==3.1.0 coverage==7.2.7 -cryptography==41.0.1 +cryptography==41.0.4 defusedxml==0.7.1 Deprecated==1.2.14 dj-rest-auth==4.0.1 From 7a5be1248c8730555399a5d348d6de1dc5a7ea3f Mon Sep 17 00:00:00 2001 From: Lukas Ostheimer <131896156+LukasOsti@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:01:49 +0200 Subject: [PATCH 02/40] feat: added new equivalence --- frontendVue3/src/classes/Constraint/Equivalence.js | 7 +++++++ frontendVue3/src/components/ConstraintAddEditDialog.vue | 9 ++++++++- frontendVue3/src/components/Constraints.vue | 4 ---- .../src/services/booleanExpressionParser.service.js | 7 +++++-- frontendVue3/src/services/xmlTranspiler.service.js | 3 +++ 5 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 frontendVue3/src/classes/Constraint/Equivalence.js diff --git a/frontendVue3/src/classes/Constraint/Equivalence.js b/frontendVue3/src/classes/Constraint/Equivalence.js new file mode 100644 index 00000000..146026cb --- /dev/null +++ b/frontendVue3/src/classes/Constraint/Equivalence.js @@ -0,0 +1,7 @@ +import { GroupConstraintItem } from '@/classes/Constraint/GroupConstraintItem'; + +export class Equivalence extends GroupConstraintItem { + constructor(first, second) { + super(first, second, '⇔', 'EQUI', 'eq'); + } +} diff --git a/frontendVue3/src/components/ConstraintAddEditDialog.vue b/frontendVue3/src/components/ConstraintAddEditDialog.vue index db65737e..b689433f 100644 --- a/frontendVue3/src/components/ConstraintAddEditDialog.vue +++ b/frontendVue3/src/components/ConstraintAddEditDialog.vue @@ -41,6 +41,13 @@ >implies + + equi + + - diff --git a/frontendVue3/src/components/Constraints.vue b/frontendVue3/src/components/Constraints.vue index a457ebb2..aa13adf4 100644 --- a/frontendVue3/src/components/Constraints.vue +++ b/frontendVue3/src/components/Constraints.vue @@ -105,10 +105,6 @@ import { AddCommand } from '@/classes/Commands/Constraints/AddCommand'; import { CommandManager } from '@/classes/Commands/CommandManager'; import { EditCommand } from '@/classes/Commands/Constraints/EditCommand'; import { DeleteCommand } from '@/classes/Commands/Constraints/DeleteCommand'; -import * as init from '@/services/FeatureModel/init.service'; -import * as dragAndDrop from '@/services/FeatureModel/dragAndDrop.service'; -import * as view from '@/services/FeatureModel/view.service'; -import * as update from '@/services/FeatureModel/update.service'; export default { name: 'Constraints', diff --git a/frontendVue3/src/services/booleanExpressionParser.service.js b/frontendVue3/src/services/booleanExpressionParser.service.js index 547029f4..aa950bcf 100644 --- a/frontendVue3/src/services/booleanExpressionParser.service.js +++ b/frontendVue3/src/services/booleanExpressionParser.service.js @@ -3,8 +3,9 @@ import {Implication} from "@/classes/Constraint/Implication"; import {Conjunction} from "@/classes/Constraint/Conjunction"; import {Disjunction} from "@/classes/Constraint/Disjunction"; import {FeatureNodeConstraintItem} from "@/classes/Constraint/FeatureNodeConstraintItem"; +import { Equivalence } from '@/classes/Constraint/Equivalence'; -const operators = ['not', 'implies', 'and', 'or']; +const operators = ['not', 'implies', 'and', 'or', 'equi']; export const operatorPrecedence = {}; operators.forEach((operator, i) => operatorPrecedence[operator] = i); @@ -83,6 +84,8 @@ function convertToConstraintItem(operator, stack) { constraintItem = new Disjunction(first, second); } else if (operator === 'implies') { constraintItem = new Implication(first, second); + } else if (operator === 'equi') { + constraintItem = new Equivalence(first, second); } } @@ -95,4 +98,4 @@ function createFeatureNodeConstraintItem(featureNodeName, allNodes) { throw Error(`FeatureNode '${featureNodeName} cannot be found`); } return new FeatureNodeConstraintItem(foundNode); -} \ No newline at end of file +} diff --git a/frontendVue3/src/services/xmlTranspiler.service.js b/frontendVue3/src/services/xmlTranspiler.service.js index 1dfcffc1..c3263c68 100644 --- a/frontendVue3/src/services/xmlTranspiler.service.js +++ b/frontendVue3/src/services/xmlTranspiler.service.js @@ -6,6 +6,7 @@ import { Conjunction } from '@/classes/Constraint/Conjunction'; import { Implication } from '@/classes/Constraint/Implication'; import { Negation } from '@/classes/Constraint/Negation'; import { SoloDisjunction } from '@/classes/Constraint/SoloDisjunction'; +import { Equivalence } from '@/classes/Constraint/Equivalence'; export function xmlToJson(currentModel, data) { /*const start = performance.now();*/ @@ -93,6 +94,8 @@ function readConstraintItem(item, data) { return new Conjunction(childItems[0], childItems[1]); case 'imp': return new Implication(childItems[0], childItems[1]); + case 'eq': + return new Equivalence(childItems[0], childItems[1]); case 'not': return new Negation(childItems[0]); } From 99f1e273940ac2ce22559016615110cfbb70cbca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Oct 2023 21:25:25 +0000 Subject: [PATCH 03/40] Bump postcss from 8.4.24 to 8.4.31 in /frontendVue3 Bumps [postcss](https://github.com/postcss/postcss) from 8.4.24 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.24...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontendVue3/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontendVue3/package-lock.json b/frontendVue3/package-lock.json index b65ee61a..b2c8fa3a 100644 --- a/frontendVue3/package-lock.json +++ b/frontendVue3/package-lock.json @@ -2444,9 +2444,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", From 1ab6a2db0fc0525b1f4a703c83b78472d4b46c55 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:55:58 +0200 Subject: [PATCH 04/40] Feat. Added Indeces for Files and Families --- ..._dockerprocess_file_to_analyse_and_more.py | 27 ++++++++++++ ...core_fileup_owner_i_4178bf_idx_and_more.py | 25 +++++++++++ ...core_fileup_owner_i_4e6325_idx_and_more.py | 41 +++++++++++++++++++ ...core_fileup_owner_i_4178bf_idx_and_more.py | 29 +++++++++++++ ...core_fileup_family__df0347_idx_and_more.py | 29 +++++++++++++ backend/core/fileupload/models.py | 16 +++++++- 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py create mode 100644 backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py create mode 100644 backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py create mode 100644 backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py create mode 100644 backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py diff --git a/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py b/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py new file mode 100644 index 00000000..eb9822a2 --- /dev/null +++ b/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_analysis', '0018_alter_dockerprocess_resources'), + ] + + operations = [ + migrations.RemoveField( + model_name='dockerprocess', + name='file_to_analyse', + ), + migrations.RemoveField( + model_name='dockerprocess', + name='owner', + ), + migrations.DeleteModel( + name='Analysis', + ), + migrations.DeleteModel( + name='DockerProcess', + ), + ] diff --git a/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py b/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py new file mode 100644 index 00000000..6ba84be9 --- /dev/null +++ b/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0011_analysisresult'), + ] + + operations = [ + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner', 'confirmation_token'], name='core_fileup_owner_i_4178bf_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['confirmation_token'], name='core_fileup_confirm_22c787_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['family', 'version', 'is_confirmed'], name='core_fileup_family__df0347_idx'), + ), + ] diff --git a/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py b/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py new file mode 100644 index 00000000..5b1fcedd --- /dev/null +++ b/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0012_file_core_fileup_owner_i_4178bf_idx_and_more'), + ] + + operations = [ + migrations.AddIndex( + model_name='family', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_4e6325_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_3c733f_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['family'], name='core_fileup_family__57b383_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['version'], name='core_fileup_version_b47e16_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['is_confirmed'], name='core_fileup_is_conf_ba01cb_idx'), + ), + migrations.AddIndex( + model_name='license', + index=models.Index(fields=['label'], name='core_fileup_label_70ea9c_idx'), + ), + migrations.AddIndex( + model_name='tag', + index=models.Index(fields=['label'], name='core_fileup_label_fa57bd_idx'), + ), + ] diff --git a/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py b/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py new file mode 100644 index 00000000..afc34980 --- /dev/null +++ b/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.2 on 2023-10-12 11:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0013_family_core_fileup_owner_i_4e6325_idx_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='file', + name='core_fileup_owner_i_4178bf_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_confirm_22c787_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_owner_i_3c733f_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_version_b47e16_idx', + ), + ] diff --git a/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py b/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py new file mode 100644 index 00000000..061bca05 --- /dev/null +++ b/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.2 on 2023-10-13 06:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='file', + name='core_fileup_family__df0347_idx', + ), + migrations.RemoveIndex( + model_name='license', + name='core_fileup_label_70ea9c_idx', + ), + migrations.RemoveIndex( + model_name='tag', + name='core_fileup_label_fa57bd_idx', + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_3c733f_idx'), + ), + ] diff --git a/backend/core/fileupload/models.py b/backend/core/fileupload/models.py index 3816bc31..979a3b55 100644 --- a/backend/core/fileupload/models.py +++ b/backend/core/fileupload/models.py @@ -20,6 +20,9 @@ class Family(models.Model): class Meta: verbose_name = "family" verbose_name_plural = "families" + indexes = [ + models.Index(fields=['owner']), + ] def __str__(self): # do not change that @@ -160,6 +163,13 @@ class File(models.Model): slug = models.SlugField(null=True) confirmation_token = models.CharField(default="", max_length=255) + class Meta: + indexes = [ + models.Index(fields=['owner']), + models.Index(fields=['family']), + models.Index(fields=['is_confirmed']), + ] + def __str__(self): # do not change that return f"{self.id}" @@ -169,17 +179,19 @@ def save(self, *args, **kwargs): # new self.slug = slugify(self.label) return super().save(*args, **kwargs) + class Analysis(models.Model): admin_only = models.BooleanField(default=False) disabled = models.BooleanField(default=False) query = models.TextField() - + depends_on = models.ManyToManyField("self", symmetrical=False) + class AnalysisResult(models.Model): triggered = models.BooleanField(default=False) error = models.BooleanField(default=False) result = models.JSONField(null=True) - + analysis = models.ForeignKey(Analysis, on_delete=models.CASCADE) file = models.ForeignKey(File, on_delete=models.CASCADE) From 526ef9e034e9178edf4337bf6773e2f5f49923ec Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:57:21 +0200 Subject: [PATCH 05/40] Feat.: optimizized Database Access for ConfirmedUpload File --- backend/core/fileupload/viewsets.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 0b6c32ab..09bc7297 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -1,3 +1,5 @@ +from django.db.models import Q + from core.fileupload.models import Family, Tag, License, File, Analysis, AnalysisResult from core.fileupload.utils import generate_random_string from core.fileupload.serializers import ( @@ -383,17 +385,18 @@ def list(self, request, **kwargs): indicating if the user which has sent the request is the owner. """ queryset = File.objects.filter(is_confirmed=True) - familyId = self.request.query_params.get("family") - if familyId is not None: - queryset = queryset.filter(family__id=familyId).order_by("version") + family_id = self.request.query_params.get("family") owner = self.request.query_params.get("owner") + + filter_conditions = Q(is_confirmed=True) + if family_id is not None: + filter_conditions &= Q(family__id=family_id) if owner is not None: - queryset = queryset.filter(owner=owner) - files = FilesSerializer(queryset, many=True).data - anonymized_files = [] - for file in files: - anonymized_file = anonymize_file(file, request) - anonymized_files.append(anonymized_file) + filter_conditions &= Q(owner=owner) + queryset = (queryset.filter(filter_conditions).select_related('family', 'license'). + prefetch_related('tags').order_by("version")) + + anonymized_files = [anonymize_file(FilesSerializer(file).data, request) for file in queryset] return Response(anonymized_files) def retrieve(self, request, *args, **kwargs): From 527af63b75c849cb18c00283c4c59fed35564bec Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:23:56 +0200 Subject: [PATCH 06/40] Feat: Implemented Caching for Families and Confirmed Models --- backend/core/fileupload/viewsets.py | 53 ++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 09bc7297..05e85b81 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -1,4 +1,6 @@ +from django.core.cache import cache from django.db.models import Q +from django.utils.decorators import method_decorator from core.fileupload.models import Family, Tag, License, File, Analysis, AnalysisResult from core.fileupload.utils import generate_random_string @@ -39,6 +41,7 @@ from multiprocessing import Process from rest_framework.views import APIView from django.http.request import QueryDict +from django.views.decorators.cache import cache_page import json import os @@ -204,16 +207,16 @@ class BulkUploadApiView(UploadApiView): def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: files = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) serializers = [] confirmation_token = generate_random_string(30) @@ -266,16 +269,16 @@ class ZipUploadApiView(UploadApiView): def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: file_data = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) label = file_data["label"] description = file_data["description"] @@ -301,7 +304,7 @@ def post(self, request, format=None): validated_data["label"] = label validated_data["description"] = description validated_data["license"] = license - validated_data["version"] = f"{i+1}.0.0" + validated_data["version"] = f"{i + 1}.0.0" validated_data["family"] = family validated_data.setlist("tags", tags) validated_data["local_file"] = local_file @@ -384,10 +387,22 @@ def list(self, request, **kwargs): Replace email address of file owner with True or False, indicating if the user which has sent the request is the owner. """ - queryset = File.objects.filter(is_confirmed=True) + family_id = self.request.query_params.get("family") owner = self.request.query_params.get("owner") + if family_id is not None and owner is not None: + cache_key = f"confirmed_files_{family_id}_{owner}" + elif family_id is not None: + cache_key = f"confirmed_files_{family_id}_all" + elif owner is not None: + cache_key = f"confirmed_files_all_{owner}" + else: + cache_key = "confirmed_files_all" + cached_data = cache.get(cache_key) + if cached_data is not None: + return Response(cached_data) + queryset = File.objects.filter(is_confirmed=True) filter_conditions = Q(is_confirmed=True) if family_id is not None: filter_conditions &= Q(family__id=family_id) @@ -397,6 +412,7 @@ def list(self, request, **kwargs): prefetch_related('tags').order_by("version")) anonymized_files = [anonymize_file(FilesSerializer(file).data, request) for file in queryset] + cache.set(cache_key, anonymized_files, 60 * 15) return Response(anonymized_files) def retrieve(self, request, *args, **kwargs): @@ -434,11 +450,15 @@ def anonymize_family(self, family, request): return anonymized_family def list(self, request, **kwargs): + cached_families = cache.get("families_cache_key") + if cached_families is not None: + return Response(cached_families) queryset = Family.objects.all() families = FamiliesSerializer(queryset, many=True).data anonymized_families = [] for family in families: anonymized_families.append(self.anonymize_family(family, request)) + cache.set("families_cache_key", anonymized_families, 60 * 15) return Response(anonymized_families) def retrieve(self, request, *args, **kwargs): @@ -449,6 +469,7 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + cache.delete("families_cache_key") class LicensesViewSet( @@ -524,6 +545,7 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + class AnalysesViewSet( viewsets.GenericViewSet, mixins.CreateModelMixin, @@ -531,7 +553,6 @@ class AnalysesViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, ): - queryset = Analysis.objects.all() serializer_class = AnalysesSerializer permission_classes = [ @@ -543,12 +564,12 @@ def list(self, request, **kwargs): analyses = AnalysesSerializer(queryset, many=True).data return Response(analyses) + class AnalysisResultsViewSet( viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin, ): - queryset = AnalysisResult.objects.all() serializer_class = AnalysisResultsSerializer From e9432178d5fe52ab461038a316af35b666405bd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 20:37:22 +0000 Subject: [PATCH 07/40] Bump urllib3 from 2.0.3 to 2.0.7 in /backend Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.3 to 2.0.7. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.3...2.0.7) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index c2916e42..1f788431 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -35,6 +35,6 @@ requests-oauthlib==1.3.1 six==1.16.0 sqlparse==0.4.4 toposort==1.10 -urllib3==2.0.3 +urllib3==2.0.7 websocket-client==1.6.0 wrapt==1.15.0 From a49b4214268e037425d7c8e8fb0075ef19b358c7 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:58:49 +0200 Subject: [PATCH 08/40] Feat: Impl Owner specific cache for families --- backend/core/fileupload/viewsets.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 05e85b81..752d0afc 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -1,6 +1,6 @@ from django.core.cache import cache from django.db.models import Q -from django.utils.decorators import method_decorator +from rest_framework.request import Request from core.fileupload.models import Family, Tag, License, File, Analysis, AnalysisResult from core.fileupload.utils import generate_random_string @@ -40,8 +40,7 @@ import core.fileupload.githubmirror.github_manager as gm from multiprocessing import Process from rest_framework.views import APIView -from django.http.request import QueryDict -from django.views.decorators.cache import cache_page +from django.http.request import QueryDict, HttpRequest import json import os @@ -220,7 +219,6 @@ def post(self, request, format=None): serializers = [] confirmation_token = generate_random_string(30) - for uploaded_file in files: label = uploaded_file["label"] description = uploaded_file["description"] @@ -294,7 +292,6 @@ def post(self, request, format=None): serializers = [] confirmation_token = generate_random_string(30) - for (i, uploaded_file) in enumerate(files.infolist()): local_file = SimpleUploadedFile( f"{generate_random_string(20)}.xml", files.read(uploaded_file) @@ -450,7 +447,7 @@ def anonymize_family(self, family, request): return anonymized_family def list(self, request, **kwargs): - cached_families = cache.get("families_cache_key") + cached_families = cache.get(f"families_cache_{request.user}") if cached_families is not None: return Response(cached_families) queryset = Family.objects.all() @@ -458,7 +455,7 @@ def list(self, request, **kwargs): anonymized_families = [] for family in families: anonymized_families.append(self.anonymize_family(family, request)) - cache.set("families_cache_key", anonymized_families, 60 * 15) + cache.set(f"families_cache_{request.user}", anonymized_families, 60 * 15) return Response(anonymized_families) def retrieve(self, request, *args, **kwargs): @@ -469,7 +466,8 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) - cache.delete("families_cache_key") + cache_key = f"families_cache_{self.request.user}" + cache.delete(cache_key) class LicensesViewSet( From 050b77ed7e4855cffabc63a0fa551845491584c8 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:59:54 +0200 Subject: [PATCH 09/40] Feat: Instantly create new Cache on FileConfirm --- backend/core/fileupload/viewsets.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 752d0afc..0940adb5 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -102,6 +102,19 @@ def get(self, request, token): file_data = [] + django_request = HttpRequest() + django_request.method = 'GET' + django_request.META = request.META + django_request.GET = request.query_params.dict() + + request_with_family = HttpRequest() + request_with_family.method = 'GET' + request_with_family.user = request.user + + request_with_owner = HttpRequest() + request_with_owner.method = 'GET' + request_with_owner.user = request.user + for file in files: if file.is_confirmed: return Response( @@ -109,8 +122,20 @@ def get(self, request, token): ) file.is_confirmed = True file.save() + cache.delete_many([f'confirmed_files_{file.owner}', f'confirmed_files_{file.family.id}']) file_data.append(FilesSerializer(file).data) + request_with_family.GET['family'] = file.family.id + request_with_owner.GET['owner'] = file.owner.id + + cache.delete('confirmed_files_all') + confirmed_file_view = ConfirmedFileViewSet.as_view({'get': 'list'}) + confirmed_file_view(django_request) + ConfirmedFileViewSet.as_view({'get': 'list'})(request_with_family) + ConfirmedFileViewSet.as_view({'get': 'list'})(request_with_owner) + + response_with_family = confirmed_file_view(django_request) + return Response({"files": file_data}, status.HTTP_201_CREATED) From b7ae2b9c275f32f5916d56f6364537366f2c67ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:08:55 +0000 Subject: [PATCH 10/40] Bump @babel/traverse from 7.22.5 to 7.23.2 in /frontend Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.5 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/package-lock.json | 202 +++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 100 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2e365fae..a9bee24b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -77,12 +77,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -128,12 +129,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -257,22 +258,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -414,9 +415,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -435,9 +436,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -482,13 +483,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -496,9 +497,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1787,33 +1788,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1822,13 +1823,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -17426,12 +17427,13 @@ } }, "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -17464,12 +17466,12 @@ } }, "@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -17562,19 +17564,19 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -17680,9 +17682,9 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { "@babel/types": "^7.22.5" @@ -17695,9 +17697,9 @@ "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -17730,20 +17732,20 @@ } }, "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -18601,42 +18603,42 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, From d752da99273e726facc67971245cb95eb10d76e6 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:28 +0200 Subject: [PATCH 11/40] Refactor: Adapted Multiple Upload Card --- .../upload_cards/file_create/Multiple.vue | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Multiple.vue b/frontendVue3/src/components/upload_cards/file_create/Multiple.vue index 15cdd40c..8ba2f7fd 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Multiple.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Multiple.vue @@ -25,21 +25,21 @@ show-size > - - - - + + + + + @@ -161,7 +161,7 @@ let formData = reactive({ label: '', description: '', files: null, - license: null, + license: "CC BY - SA 4.0 DEED", family: null, version: '', tags: [], @@ -174,11 +174,6 @@ const showDetails = ref(false); let fileRules = [(v) => !!v || 'File is required']; -let { licenses } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; - -let familyRules = [(v) => !!v || 'Family is required']; - let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; From 5899183ca9081e6a827bd1fd6690f23db52775d2 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:35 +0200 Subject: [PATCH 12/40] Refactor: Adapted Single Upload Card --- .../upload_cards/file_create/Single.vue | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Single.vue b/frontendVue3/src/components/upload_cards/file_create/Single.vue index ae716572..dd000493 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Single.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Single.vue @@ -2,6 +2,20 @@ + + + - - - - - - - + + + + + @@ -67,7 +68,6 @@ item-title="label" item-value="id" :required="true" - :rules="familyRules" variant="outlined" density="comfortable" hint="Add to or create new family" @@ -176,7 +176,7 @@ + + From 0b913b4f430d6de603052f005b80cfc381092faf Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:44 +0200 Subject: [PATCH 13/40] Refactor: Adapted Zip Upload Card --- .../upload_cards/file_create/Zip.vue | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Zip.vue b/frontendVue3/src/components/upload_cards/file_create/Zip.vue index 22c56dab..9cce6ed5 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Zip.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Zip.vue @@ -2,6 +2,20 @@ + + + + - - - - - - + + + + @@ -126,7 +127,7 @@ !!v || 'Label is required']; let descriptionRules = [ - (v) => !!v || 'Description is required', (v) => v.length <= 250 || 'Max 250 characters please', ]; let fileRules = [(v) => !!v || 'File is required']; -let { licenses, tags, myOwnTags } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; +let { tags, myOwnTags } = storeToRefs(fileStore); -let familyRules = [(v) => !!v || 'Family is required']; let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; -//let versionRules = [(v) => !!v || 'Version is required']; - let addTagMenu = ref(false); let addFamilyMenu = ref(false); +const redirectToLicensePage = () => { + window.open('https://creativecommons.org/licenses/by-sa/4.0/deed.de', '_blank'); +}; From e21e4d2af76e1290dd4a1b940d99d5e975bac7e9 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:45:09 +0200 Subject: [PATCH 14/40] Feat: Implemented DefaultLicense --- .../upload_cards/file_create/ActionButtons.vue | 11 ++++++----- frontendVue3/src/store/file.js | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue b/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue index b2e8ee82..0d35ca8c 100644 --- a/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue +++ b/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue @@ -24,11 +24,12 @@ From 282265a8964a33375955114b8cd3ec669e64c176 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:45:09 +0200 Subject: [PATCH 19/40] Fix: Added TagsFIlter for all Views --- .../src/components/FeatureModelTable.vue | 16 +++++++++++++--- frontendVue3/src/views/HistoryDetail.vue | 12 ++++++------ frontendVue3/src/views/Home.vue | 5 +++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/frontendVue3/src/components/FeatureModelTable.vue b/frontendVue3/src/components/FeatureModelTable.vue index a6c281e7..3e5f3faf 100644 --- a/frontendVue3/src/components/FeatureModelTable.vue +++ b/frontendVue3/src/components/FeatureModelTable.vue @@ -21,20 +21,21 @@ vertical > - + > {{ new Date(item.raw.uploaded_at).toLocaleString('en-US') }} - + @@ -289,6 +290,15 @@ const createDialog = ref(false); const checkLocalStorage = computed(() => { return !!localStorage.featureModelData; }); +const noDataMessage = computed(() => { + // Wenn entweder die Suche oder die Tags aktiviert sind und keine passenden Elemente gefunden wurden + if ((search.value || selectedTags.value.length > 0) && filteredItems.value.length === 0) { + return "No Feature Model matches the search criteria."; + } else { + // Wenn weder Suche noch Tags aktiviert sind oder passende Elemente gefunden wurden + return props.noDataText; + } +}); const filteredItems = computed(() => { // Wenn die Suche leer ist, zeige alle Elemente if (!search.value && selectedTags.value.length === 0) { diff --git a/frontendVue3/src/views/HistoryDetail.vue b/frontendVue3/src/views/HistoryDetail.vue index 2ee73f46..1805e2b4 100644 --- a/frontendVue3/src/views/HistoryDetail.vue +++ b/frontendVue3/src/views/HistoryDetail.vue @@ -221,7 +221,7 @@ :no-data-text="`No feature models in ${family.label} yet`" :addable="false" headline="Feature Models of Family" - /> + :available-tags="tags"/> @@ -235,16 +235,15 @@ import {busyBoxConfigs} from "@/assets/busyBoxAnalyzeExample"; import {useDisplay, useTheme} from "vuetify"; import {VSkeletonLoader} from 'vuetify/labs/VSkeletonLoader' import LineChart from '@/components/Charts/LineChart.vue'; - - - - - +import {useFileStore} from "@/store/file"; +import {storeToRefs} from "pinia"; const breakpoints = useDisplay(); const theme = useTheme(); const route = useRoute(); const API_URL = import.meta.env.VITE_APP_DOMAIN; +const fileStore = useFileStore(); +const { tags } = storeToRefs(useFileStore()); const family = ref({}); const files = ref([]); @@ -364,5 +363,6 @@ function onElementHover(elem) { onMounted(async () => { await getFamily(); await fetchFeatureModelOfFamily(); + fileStore.fetchTags(); }); diff --git a/frontendVue3/src/views/Home.vue b/frontendVue3/src/views/Home.vue index 470e38dc..ddf09111 100644 --- a/frontendVue3/src/views/Home.vue +++ b/frontendVue3/src/views/Home.vue @@ -8,6 +8,7 @@ @@ -37,7 +38,7 @@ import { onMounted, ref } from 'vue'; import { storeToRefs } from 'pinia'; import { useFileStore } from '@/store/file'; -const { confirmedFeatureModels } = storeToRefs(useFileStore()); +const { confirmedFeatureModels, tags } = storeToRefs(useFileStore()); const fileStore = useFileStore(); const search = ''; @@ -79,7 +80,6 @@ const defaultItem = { }; const licenses = []; const families = []; -const tags = []; const check1 = false; const check2 = false; const check3 = false; @@ -127,5 +127,6 @@ const tutorialSteps = [ onMounted(() => { fileStore.fetchConfirmedFeatureModels(); + fileStore.fetchTags(); }); From 90a1395b9753ff6c720109c62bd8e2eb6279bf9a Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:19:09 +0200 Subject: [PATCH 20/40] Fix: Removed Register Button --- frontendVue3/src/components/Navbar.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontendVue3/src/components/Navbar.vue b/frontendVue3/src/components/Navbar.vue index 45e4bcea..f6fab250 100644 --- a/frontendVue3/src/components/Navbar.vue +++ b/frontendVue3/src/components/Navbar.vue @@ -85,13 +85,6 @@ >
Admin
- - Register - Date: Fri, 27 Oct 2023 12:57:03 +0200 Subject: [PATCH 21/40] Feat: Added Private Field for File Model --- .../fileupload/migrations/0014_file_private.py | 18 ++++++++++++++++++ backend/core/fileupload/models.py | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 backend/core/fileupload/migrations/0014_file_private.py diff --git a/backend/core/fileupload/migrations/0014_file_private.py b/backend/core/fileupload/migrations/0014_file_private.py new file mode 100644 index 00000000..bb4070f8 --- /dev/null +++ b/backend/core/fileupload/migrations/0014_file_private.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-10-26 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0013_file_private_alter_file_family_alter_file_version'), + ] + + operations = [ + migrations.AddField( + model_name='file', + name='private', + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/core/fileupload/models.py b/backend/core/fileupload/models.py index 1b1eec76..0bf48814 100644 --- a/backend/core/fileupload/models.py +++ b/backend/core/fileupload/models.py @@ -119,8 +119,9 @@ def save_file(self, local_file, **kwargs): raise TypeError("Tags is not set") family = kwargs.get("family", None) version = kwargs.get("version", None) + private = kwargs.get("private", None) # get license from id - if kwargs.get("license", None) is None: + if kwargs.get("license", None) is None and kwargs.get("private") is None: raise TypeError("License not set!") file = self.model(**kwargs) @@ -176,6 +177,9 @@ class File(models.Model): ) # indicates if the user confirmed the upload slug = models.SlugField(null=True) confirmation_token = models.CharField(default="", max_length=255) + private = models.BooleanField( + default=False + ) def __str__(self): # do not change that From 482c754783a3a9c86418f436ffd24df4b4f433e5 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:57:44 +0200 Subject: [PATCH 22/40] Feat: Added new ViewSets for PrivateFileUpload --- backend/core/fileupload/viewsets.py | 105 +++++++++++++++++++++++----- backend/core/routers.py | 4 +- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 037611b8..4e7cf4a1 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -180,7 +180,6 @@ def check_family(self, request, family_id): return family and family.owner == request.user return True - def check_tags(self, request, tag_ids): for tag_id in tag_ids: tag = Tag.objects.get(pk=tag_id) @@ -204,21 +203,72 @@ def schedule_analysis(self, fs): analysis_result.save() -class BulkUploadApiView(UploadApiView): +class PrivateFileUploadApiView(APIView): + permission_classes = [permissions.IsAuthenticated] + def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: files = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + for uploaded_file in files: + label = uploaded_file["label"] + description = uploaded_file["description"] + file = request.FILES[uploaded_file["file"]] + license = uploaded_file["license"] + tags = uploaded_file["tags"] + + if not label or not file: + return Response( + {"message": "Missing required fields"}, + status=status.HTTP_400_BAD_REQUEST, ) + validated_data = QueryDict("", mutable=True) + validated_data["label"] = label + validated_data["description"] = description + validated_data.setlist("tags", tags) + validated_data["local_file"] = file + validated_data["private"] = True + validated_data["license"] = license + + fs = FilesSerializer(data=validated_data) + if fs.is_valid(): + fs.save(owner=request.user) + return Response( + {"message": "File uploaded successfully"}, + status=status.HTTP_201_CREATED, + ) + else: + return Response( + {"message": "Invalid file format"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class BulkUploadApiView(UploadApiView): + def post(self, request, format=None): + if not request.data["files"]: + return Response( + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) + try: + files = json.loads(request.data["files"]) + except: + return Response( + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + serializers = [] confirmation_token = generate_random_string(30) @@ -270,16 +320,16 @@ class ZipUploadApiView(UploadApiView): def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: file_data = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) label = file_data["label"] description = file_data["description"] @@ -305,7 +355,7 @@ def post(self, request, format=None): validated_data["label"] = label validated_data["description"] = description validated_data["license"] = license - validated_data["version"] = f"{i+1}.0.0" + validated_data["version"] = f"{i + 1}.0.0" validated_data["family"] = family validated_data.setlist("tags", tags) validated_data["local_file"] = local_file @@ -411,6 +461,29 @@ def retrieve(self, request, *args, **kwargs): return Response(anonymized_file) +class PrivateFileViewSet(viewsets.ModelViewSet): + queryset = File.objects.filter(private=True) + serializer_class = FilesSerializer + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrIsAdminOrReadOnly] + + def list(self, request, *args, **kwargs): + queryset = self.queryset.filter(owner=request.user) + serializer = self.serializer_class(queryset, many=True) + files = serializer.data + anonymized_files = [] + for file in files: + anonymized_file = anonymize_file(file, request) + anonymized_files.append(anonymized_file) + return Response(anonymized_files) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + if instance.owner != request.user: + return Response({"error": "Access denied"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.serializer_class(instance) + return Response(serializer.data) + + class FamiliesViewSet( viewsets.GenericViewSet, mixins.ListModelMixin, @@ -527,6 +600,7 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + class AnalysesViewSet( viewsets.GenericViewSet, mixins.CreateModelMixin, @@ -534,7 +608,6 @@ class AnalysesViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, ): - queryset = Analysis.objects.all() serializer_class = AnalysesSerializer permission_classes = [ @@ -546,12 +619,12 @@ def list(self, request, **kwargs): analyses = AnalysesSerializer(queryset, many=True).data return Response(analyses) + class AnalysisResultsViewSet( viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin, ): - queryset = AnalysisResult.objects.all() serializer_class = AnalysisResultsSerializer diff --git a/backend/core/routers.py b/backend/core/routers.py index 617ce2fc..fb014092 100644 --- a/backend/core/routers.py +++ b/backend/core/routers.py @@ -13,7 +13,7 @@ ConfirmedFileViewSet, UnconfirmedFileViewSet, ConfirmFileUploadApiView, - DeleteFileUploadApiView, + DeleteFileUploadApiView, PrivateFileUploadApiView, PrivateFileViewSet, ) from core.user.viewsets import ActivateUserViewSet, UserInfoApiView from core.auth.viewsets import LoginViewSet, RegistrationViewSet, RefreshViewSet @@ -41,6 +41,7 @@ router.register( r"files/uploaded/confirmed", ConfirmedFileViewSet, basename="confirmed-files" ) +router.register(r'files/uploaded/private', PrivateFileViewSet, basename='private-files') router.register( r"files/uploaded/unconfirmed", UnconfirmedFileViewSet, basename="unconfirmed-files" ) @@ -55,6 +56,7 @@ *router.urls, path("bulk-upload/", BulkUploadApiView.as_view()), path("zip-upload/", ZipUploadApiView.as_view()), + path("private-upload/", PrivateFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/confirm/(?P[\w\d]+)", ConfirmFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/delete/(?P[\w\d]+)", DeleteFileUploadApiView.as_view()), path("api-auth/", include("rest_framework.urls")), From 1fc3561abc0d65e459197dc44bb13f86d32d9c91 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:59:24 +0200 Subject: [PATCH 23/40] Feat: Implemented Possibility to upload private Feature from LocalStore --- .../src/components/FeatureModelTable.vue | 40 +++++++- .../file_create/ActionButtons.vue | 26 ++++- .../file_create/PrivateUpload.vue | 96 +++++++++++++++++++ frontendVue3/src/store/file.js | 31 ++++++ frontendVue3/src/views/Models.vue | 4 +- 5 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 frontendVue3/src/components/upload_cards/file_create/PrivateUpload.vue diff --git a/frontendVue3/src/components/FeatureModelTable.vue b/frontendVue3/src/components/FeatureModelTable.vue index 3e5f3faf..afe787db 100644 --- a/frontendVue3/src/components/FeatureModelTable.vue +++ b/frontendVue3/src/components/FeatureModelTable.vue @@ -54,6 +54,12 @@ density="comfortable" > + + + See local storage + + + Upload from local storage - + > + mdi-school + + +