diff --git a/app/api/exceptions.py b/app/api/exceptions.py index 3a5a679f..6911cf91 100644 --- a/app/api/exceptions.py +++ b/app/api/exceptions.py @@ -1,5 +1,5 @@ from rest_framework import status -from rest_framework.exceptions import APIException, PermissionDenied +from rest_framework.exceptions import APIException, PermissionDenied, NotFound, ValidationError class FileParseException(APIException): @@ -20,3 +20,11 @@ class AutoLabelingException(APIException): class AutoLabeliingPermissionDenied(PermissionDenied): default_detail = 'You do not have permission to perform auto labeling.' \ 'Please ask the project administrators to add you.' + + +class URLConnectionError(NotFound): + default_detail = 'Failed to establish a connection. Please check the URL or network.' + + +class AWSTokenError(ValidationError): + default_detail = 'The security token included in the request is invalid.' diff --git a/app/api/serializers.py b/app/api/serializers.py index 05056031..e07d693e 100644 --- a/app/api/serializers.py +++ b/app/api/serializers.py @@ -268,5 +268,11 @@ class AutoLabelingConfigSerializer(serializers.ModelSerializer): try: RequestModelFactory.create(data['model_name'], data['model_attrs']) except Exception: - raise serializers.ValidationError('The attributes does not match the model.') + model = RequestModelFactory.find(data['model_name']) + schema = model.schema() + required_fields = ', '.join(schema['required']) if 'required' in schema else '' + raise serializers.ValidationError( + 'The attributes does not match the model.' + 'You need to correctly specify the required fields: {}'.format(required_fields) + ) return data diff --git a/app/api/views.py b/app/api/views.py index ad73dfcd..8cda27c7 100644 --- a/app/api/views.py +++ b/app/api/views.py @@ -2,6 +2,8 @@ import collections import json import random +import botocore.exceptions +import requests from auto_labeling_pipeline.menu import Options from auto_labeling_pipeline.models import RequestModelFactory from auto_labeling_pipeline.mappings import MappingTemplate @@ -25,7 +27,7 @@ from rest_framework.views import APIView from rest_framework.parsers import MultiPartParser from rest_framework_csv.renderers import CSVRenderer -from .exceptions import AutoLabelingException, AutoLabeliingPermissionDenied +from .exceptions import AutoLabelingException, AutoLabeliingPermissionDenied, URLConnectionError, AWSTokenError from .filters import DocumentFilter from .models import Project, Label, Document, RoleMapping, Role, Comment, AutoLabelingConfig from .permissions import IsProjectAdmin, IsAnnotatorAndReadOnly, IsAnnotator, IsAnnotationApproverAndReadOnly, IsOwnAnnotation, IsAnnotationApprover, IsOwnComment @@ -541,11 +543,12 @@ class AutoLabelingConfigTest(APIView): data={'valid': True, 'labels': output}, status=status.HTTP_200_OK ) + except requests.exceptions.ConnectionError: + raise URLConnectionError() + except botocore.exceptions.ClientError: + raise AWSTokenError() except Exception as e: - return Response( - data={'valid': False}, - status=status.HTTP_400_BAD_REQUEST - ) + raise e def pass_config_validation(self): config = self.request.data['config'] diff --git a/frontend/components/containers/settings/ConfigCreationForm.vue b/frontend/components/containers/settings/ConfigCreationForm.vue index f64a151b..c5461067 100644 --- a/frontend/components/containers/settings/ConfigCreationForm.vue +++ b/frontend/components/containers/settings/ConfigCreationForm.vue @@ -86,7 +86,7 @@

Once you fetch the API response, you can convert the label into the defined one.

- + @@ -100,6 +100,18 @@ outlined label="Sample Text" /> + + + + {{ error }} + + + @@ -163,13 +175,15 @@ export default Vue.extend({ data() { return { + errors: [] as string[], isLoading: false, passTesting: false, sampleText: '', step: new StepCounter(1, 3), templateName: null, templateConfig: {}, - templateNames: [] as string[] + templateNames: [] as string[], + labelMapping: [] } }, @@ -188,6 +202,12 @@ export default Vue.extend({ this.passTesting = false }, deep: true + }, + labelMapping: { + handler() { + this.passTesting = false + }, + deep: true } }, @@ -217,7 +237,7 @@ export default Vue.extend({ // @ts-ignore template: this.templateConfig.template, // @ts-ignore - labelMapping: this.templateConfig.label_mapping + labelMapping: this.labelMapping } return ConfigItem.parseFromUI(payload) }, @@ -229,6 +249,19 @@ export default Vue.extend({ .then(value => { this.passTesting = value.valid }) + .catch((error) => { + const data = error.response.data + this.errors = [] + if ('non_field_errors' in data) { + this.errors = data['non_field_errors'] + } else if ('template' in data) { + this.errors.push('The template need to be filled.') + } else if ('detail' in data) { + this.errors.push(data['detail']) + } else { + this.errors = data + } + }) .finally(() => { this.isLoading = false }) diff --git a/frontend/components/containers/settings/LabelMapping.vue b/frontend/components/containers/settings/LabelMapping.vue index 361a3be4..74b392ce 100644 --- a/frontend/components/containers/settings/LabelMapping.vue +++ b/frontend/components/containers/settings/LabelMapping.vue @@ -16,7 +16,7 @@ v-bind="attrs" v-on="on" > - Add a mapping + Add