0

I got a lightning component that uses to allow users to load files on Salesforce.

It has always worked fine (for months) until about a month ago (end of April, first reports).

Files are in .doc or .pdf

Now, the issue is :

  • we've been reported the issue only when trying to add a file of type__c='CV'
  • the user tries to upload a CV file by clicking 'parcourir' (FileUpload.cmp)
  • when selecting the document, the popup closes
  • the document is NOT selected ("Aucun fichier selectionné" is visible on the FileUpload.cmp component, where the local path should be displayed)

Trying F5 or clicking 'Refresh' does not resolve the issue, only opening a new Chrome tab shows the file was updated.

F5 or 'Refresh' just does nothing, as if you just arrived on the page discovering the bug (this is very weird for me, I've always been able to refresh a page, F5 has always worked in my personal and professional life). After that, users can try again and open the popup, but selecting a file does the same : file isn't selected and popup closes. At this time, users have to open a new Chrome tab to be able to avoid the bug.

Any ideas would be appreciate, This occurs sometimes and I'm unable to identify the variables that affect the bug in order to reproduce it.

About the components :

The component :

<aura:component controller="LTNG06_FileUpload">

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>

<aura:attribute name="parentId" type="Id"/>

<aura:attribute name="_types" type="AP06_SearchBase.ListValue[]" />
<aura:attribute name="_percent" type="Integer" />
<aura:attribute name="_fileType" type="String" default="CV" />

<aura:registerEvent name="fileUploaded" type="c:EVT_SimpleTrigger"/>

<aura:if isTrue="{!v._types.length > 0 }">

    <div class="container slds-show--inline-block">
        <lightning:select label="{!$Label.c.Type}" name="fileType" value="{!v._fileType}" class="fileType">
            <aura:iteration items="{!v._types}" var="type">
                <option value="{!type.value}">{!type.label}</option>
            </aura:iteration>
        </lightning:select>

        <input type="file" class="file " aura:id="file"
               id="idFile"
               onchange="{! c.setFilePathName }" /><!-- this input is hidden because we can't change the label (displaying in user's language) -->

        <span id="displayFilePath">{! $Label.c.CandidatDetail_AucunFichierChoisi }</span>
        <input type="button"
               value="{! $Label.c.CandidatDetail_Parcourir }"
               onclick="{! c.openFile }" />

        <lightning:button label="{! $Label.c.CandidatDetail_Ajouter }"
                          onclick="{!c.save}"/>

        <div class="{!(v._percent > 0 ? '' : 'slds-hide') + ' slds-progress-bar slds-progress-bar_medium slds-progress-bar_circular'}" aria-valuemin="0" aria-valuemax="100" >
            <span class="slds-progress-bar__value" style="{!'width:' + v._percent + '%;'}">

            </span>
        </div>
    </div>
</aura:if>

The Js controller :

({  
doInit : function(cmp, event, helper) {

    var action = cmp.get('c.getTypes'); 

    action.setCallback(this, function(response) {
        var state = response.getState();

        if (cmp.isValid() && state === 'SUCCESS') {
            cmp.set('v._types', response.getReturnValue());
        }
        else {
            helper.getDmlErrors(cmp, response.getError());
        }
    });

    $A.enqueueAction(action); 
},

save : function(cmp, event, helper) {
    helper.save(cmp);
},

openFile : function(cmp, event, helper) {
    document.getElementById('idFile').click();
},


/*
 * Fonction qui set le nom de la fileName pour affichage
 */ 
setFilePathName : function(cmp, event, helper) {
    var filePath = document.getElementById('idFile').value;
    var nbCar = filePath.length;
    var nbCarToDisplay = 30;

    var displayFilePath = document.getElementById('displayFilePath');
    filePath = filePath.replace('fakepath', '*'); // suppression du fakepath
    filePath = filePath.slice(-nbCarToDisplay);

    displayFilePath.innerHTML = filePath;
}

})

The helper.js :

({

MAX_FILE_SIZE: 4 500 000, /* 6 000 000 * 3/4 to account for base64 */
CHUNK_SIZE: 600 000, /* Use a multiple of 4 */


save: function (component) {
    var fileInput = component.find('file').getElement();
    var file = fileInput.files[0];

    if (!file) {
        $A.get('e.force:showToast').setParams({
            type: 'warning',
            // message: 'Vous devez sélectionner un fichier'
            message: $A.get('$Label.c.Document_MessageVousDevezSelectionnerUnFichier')
        }).fire();
        return;
    }

    if (file.size > this.MAX_FILE_SIZE) {
        $A.get('e.force:showToast').setParams({
            type: 'error',
            // message: 'La taille du fichier sélectionné (' + Math.round(file.size / 1024) + ' Ko) est supérieure à la taille maximale (' + Math.round(this.MAX_FILE_SIZE / 1024) + ' Ko)'
            message: $A.get('$Label.c.Document_LaTailleDuFichierSelectionne') + Math.round(file.size / 1024) + ' ' +  $A.get('$Label.c.Document_estSuperieurALaTailleMax') + Math.round(this.MAX_FILE_SIZE / 1024) + ' ' + $A.get('$Label.c.Document_Ko')
        }).fire();
        return;
    }

    var fr = new FileReader();

    var self = this;
    fr.onload = function () {
        var fileContents = fr.result;
        var base64Mark = 'base64,';
        var dataStart = fileContents.indexOf(base64Mark) + base64Mark.length;

        fileContents = fileContents.substring(dataStart);

        self.upload(component, file, fileContents);
    };

    fr.readAsDataURL(file);
},

upload: function (cmp, file, fileContents) {
    var fromPos = 0;
    var toPos = Math.min(fileContents.length, fromPos + this.CHUNK_SIZE);

    // start with the initial chunk
    this.uploadChunk(cmp, file, fileContents, fromPos, toPos, '');

    cmp.set('v._percent', 1);
},

uploadChunk: function (cmp, file, fileContents, fromPos, toPos, attachId) {
    var self = this;
    var a = $A.getCallback(
        function () {
            var action = cmp.get('c.saveTheChunk');
            var chunk = fileContents.substring(fromPos, toPos);

            action.setParams({
                parentId: cmp.get('v.parentId'),
                fileType: cmp.get('v._fileType'),
                fileName: file.name,
                base64Data: encodeURIComponent(chunk),
                contentType: file.type,
                fileId: attachId
            });

            action.setCallback(this, function (response) {
                var state = response.getState();

                if (cmp.isValid() && state === 'SUCCESS') {
                    attachId = response.getReturnValue();

                    cmp.set('v._percent', toPos / fileContents.length * 100);

                    fromPos = toPos;
                    toPos = Math.min(fileContents.length, fromPos + self.CHUNK_SIZE);

                    if (fromPos < toPos)
                        self.uploadChunk(cmp, file, fileContents, fromPos, toPos, attachId);
                    else {
                        cmp.set('v._percent', null);
                        cmp.getEvent('fileUploaded').fire();
                    }
                }
                else {
                    console.log(response.getError());
                    self.getDmlErrors(cmp, response.getError())
                }
            });

            $A.enqueueAction(action);
        }
    );

    a.call();
},


getDmlErrors: function (cmp, dmlErrors) {

    var errors = [];
    for (var i = 0; i < dmlErrors.length; i++) {
        for (var j = 0; j < dmlErrors[i].duplicateResults.length; j++) {
            errors.push(dmlErrors[i].duplicateResults[j].message);
        }

        for (var fieldName in dmlErrors[i].fieldErrors) {
            for (var j = 0; j < dmlErrors[i].fieldErrors[fieldName].length; j++)
                errors.push(fieldName + ' : ' + dmlErrors[i].fieldErrors[fieldName][j].message);
        }

        for (var j = 0; j < dmlErrors[i].pageErrors.length; j++) {
            errors.push(this.extractError(dmlErrors[i].pageErrors[j].message));
        }
    }

    if (errors.length > 3)
        this.showErrorToast(errors.join(', '));
    else {
        for (var i = 0; i < errors.length; i++)
            this.showErrorToast(errors[i]);
    }

    return errors;
},

extractError: function (error) {
    var index = error.indexOf('FIELD_CUSTOM_VALIDATION_EXCEPTION');
    if (index > -1) {
        error = error.substring(index + 35);
        index = error.indexOf(': []');
        if (index > -1) {
            error = error.substring(0, index);
        }
    }
    return error;
},


showErrorToast: function (errorMessage) {
    $A.get("e.force:showToast").setParams({
        title: 'Erreurs: ',
        type: 'error',
        message: errorMessage
    }).fire();
}

})

And the APEX controller LTNG06 :

public without sharing class LTNG06_FileUpload {

@AuraEnabled
public static List<AP06_SearchBase.ListValue> getTypes(){

    List<AP06_SearchBase.ListValue> types = new List<AP06_SearchBase.ListValue>();
    for(Schema.PicklistEntry opt : ContentVersion.Type__c.getDescribe().getPicklistValues()){
        types.add(new AP06_SearchBase.ListValue(opt));
    }

    return types;
}


@AuraEnabled
public static Id saveFile(Id parentId, Boolean isPublic, String fileType, String fileName, String base64Data, String contentType) {
    base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');

    //Fichiers public en ContentVersion
    if(!isPublic){
        DocumentConfidentiel__c doc = new DocumentConfidentiel__c(Name = fileName.left(80), Contact__c = parentId, Type__c = fileType);
        insert doc;

        parentId = doc.Id;
    }

    ContentVersion a = new ContentVersion();

    a.VersionData = EncodingUtil.base64Decode(base64Data);
    a.Type__c = fileType;
    a.Title = fileName;
    a.PathOnClient = fileName;
    a.IsMajorVersion = false;

    insert a;

    List<ContentVersion> listCT = new List<ContentVersion>{a};

    // récupérer l'id de la file insérée :
    a = [SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id = :a.Id LIMIT 1];

    /* 5 mai 2019 : le traitement logique doit se faire après l'insert ;
    * Néanmoins comme il existe une VR qui interdit d'avoir plus de nbCVMax et affiche un jouli
    * message d'erreur, par précaution je laisse passer un premier traitement */
    WS02_CRE.handleCVLimit(listCT, parentId);

    ContentDocumentLink l = new ContentDocumentLink();
    l.ContentDocumentId = a.ContentDocumentId;
    l.LinkedEntityId = parentId;
    l.ShareType = 'V';
    insert l;

    WS02_CRE.handleCVLimit(listCT, parentId);

    return a.Id;
}

@AuraEnabled
public static Id saveTheChunk(Id parentId, String fileType, String fileName, String base64Data, String contentType, String fileId) {

    Boolean isPublic = !getPrivateFileTypes().contains(fileType);

    if (String.isBlank(fileId)) {
        fileId = saveFile(parentId, isPublic, fileType, fileName, base64Data, contentType);
    } else {
        appendToFile(fileId, base64Data);
    }

    return Id.valueOf(fileId);
}

private static void appendToFile(Id fileId, String base64Data) {
    base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
    System.Debug('LTNG06_FileUpload: appendToFile');
    ContentVersion a = [
        SELECT Id, VersionData
        FROM ContentVersion
        WHERE Id = :fileId
        LIMIT 1];

    String existingBody = EncodingUtil.base64Encode(a.VersionData);
    a.VersionData = EncodingUtil.base64Decode(existingBody + base64Data); 

    update a;
}


public static Set<String> getPrivateFileTypes(){

    Set<String> privateFileTypes = new Set<String>();
    for(Schema.PicklistEntry opt : DocumentConfidentiel__c.Type__c.getDescribe().getPicklistValues()){
        privateFileTypes.add(opt.getValue());
    }

    return privateFileTypes;

}

/***
 * Version avec try/catch
 *
 public class LTNG06_FileUpload {

public static final Set<String> PUBLIC_FILE_TYPES = new Set<String>{'CV', 'CV blindé', 'Divers'};


@AuraEnabled
public static Id saveFile(Id parentId, Boolean isPublic, String fileType, String fileName, String base64Data, String contentType) { 
    base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');


        //Fichiers public en ContentVersion
        if(!isPublic){
            DocumentConfidentiel__c doc = new DocumentConfidentiel__c(Name = fileName.left(80), Contact__c = parentId, Type__c = fileType);
            insert doc;

            parentId = doc.Id;

        }

        ContentVersion a = new ContentVersion();

        a.VersionData = EncodingUtil.base64Decode(base64Data);
        a.Type__c = fileType;
        a.Title = fileName;
        a.PathOnClient = fileName;


        insert a;

        a = [SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id = :a.Id][0];

        ContentDocumentLink l = new ContentDocumentLink();
        l.ContentDocumentId = a.ContentDocumentId;
        l.LinkedEntityId = parentId;
        l.ShareType = 'V';
        insert l;

//      return new UploadResponse(l.Id);


    return a.Id;


}

@AuraEnabled
public static UploadResponse saveTheChunk(Id parentId, String fileType, String fileName, String base64Data, String contentType, String fileId) { 

    Boolean isPublic = PUBLIC_FILE_TYPES.contains(fileType);
    try{
        if (String.isBlank(fileId)) {
            fileId = saveFile(parentId, isPublic, fileType, fileName, base64Data, contentType);
        } else {
            appendToFile(fileId, base64Data);
        }

        return new UploadResponse(Id.valueOf(fileId));
    }
    catch(System.Exception e){
        return new UploadResponse(e);
    }

    return ;
}//

private static void appendToFile(Id fileId, String base64Data) {
    base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');

    ContentVersion a = [
        SELECT Id, VersionData
        FROM ContentVersion
        WHERE Id = :fileId
    ][0];

    String existingBody = EncodingUtil.base64Encode(a.VersionData);
    a.VersionData = EncodingUtil.base64Decode(existingBody + base64Data); 

    update a;
}


public class UploadResponse{

    @AuraEnabled public Id fileId;
    @AuraEnabled public String errorMsg;

    public UploadResponse(Id fileId){
        this.fileId = fileId;
    }

    public UploadResponse(System.Exception e){
        this.errorMsg = e.getMessage();
    }
}
 }

} */

What is displayed on screen is : enter image description here

Clicking 'parcourir' opens the popup : enter image description here

And this is the good behaviour : enter image description here

  • Any reason , lightning:fileUpload is not used? I see you are creating contentVersion and CDL in the end – Pranay Jaiswal Jun 6 at 16:32
  • Hi and thanks for answering, it's code that has been done before I arrived, from another team, and I let the custom component. I'll tcheck, for the second part of your answer ! THanks – Alexis MASSON Jun 12 at 8:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.