google-apps-scriptgoogle-sheets

Get the user that changed specific cell


I am trying to make spreadsheet with table that could by edited by anyone who is invited. However user can write only into cells that are empty or are filled by him. He cannot overwrite somebody's else work.

I was thinking about saving the editor emails and their cell notations into Properties, however with Session.getActiveUser().getEmail(), SpreadsheetApp.getActive().getActiveRange().getA1Notation() and trigger onEdit I cannot differentiate between two people, if there were adding something at the same time, I would't be able to tell who is doing what... at least I think that is how it works.

Thanks


Solution

  • Session.getActiveUser() is not accesible in the onEdit trigger if you have a normal gmail account (source: https://developers.google.com/apps-script/reference/base/session#getActiveUser())

    But I found a very cool workaround :) The tric lies in the fact that you can not remove yourself and the owner as Editors. So if you remove all editors from a protected range, you are assigning yourself as the editor (and the owner).

    In this script, the owner of the spreadsheet can overrule everybody. The others will behave as you wished: they can only edit their own entries and empty fields.

    // Test it with colors
    // var edittedBackgroundColor = "RED"; // makes the change visible, for test purposes
    // var availableBackgroundColor = "LIGHTGREEN"; //  makes the change visible, for test purposes
    
    function onEdit(e) {
      Logger.log(JSON.stringify(e));
      var alphabet = "abcdefghijklmnopqrstuvwxyz".toUpperCase().split("");
      var columnStart = e.range.columnStart;
      var rowStart = e.range.rowStart;
      var columnEnd = e.range.columnEnd;
      var rowEnd = e.range.rowEnd;
      var startA1Notation = alphabet[columnStart-1] + rowStart;
      var endA1Notation = alphabet[columnEnd-1] + rowEnd;
      var range = SpreadsheetApp.getActive().getRange(startA1Notation + ":" + endA1Notation);
    
      if(range.getValue() === "") {
        Logger.log("Cases in which the entry is empty.");
        if(typeof availableBackgroundColor !== 'undefined' && availableBackgroundColor) 
          range.setBackground(availableBackgroundColor)
        removeEmptyProtections();
        return;
      }
    
      // Session.getActiveUser() is not accesible in the onEdit trigger
      // The user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger
      // Source: https://developers.google.com/apps-script/reference/base/session#getActiveUser()
    
      var protection = range.protect().setDescription('Cell ' + startA1Notation + ' is protected');
      if(typeof edittedBackgroundColor !== 'undefined' && edittedBackgroundColor)
        range.setBackground(edittedBackgroundColor);
    
      // Though neither the owner of the spreadsheet nor the current user can be removed
      // The next line results in only the owner and current user being able to edit
    
      protection.removeEditors(protection.getEditors());
      Logger.log("These people can edit now: " + protection.getEditors());
    
      // Doublecheck for empty protections (if for any reason this was missed before)
    
      removeEmptyProtections();
    }
    
    function removeEmptyProtections() {
      var ss = SpreadsheetApp.getActive();
      var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
      for (var i = 0; i < protections.length; i++) {
        var protection = protections[i];
        if(! protection.getRange().getValue()) {
          Logger.log("Removes protection from empty field " + protection.getRange().getA1Notation());
          protection.remove();
        }
      }
      return;
    }
    
    function isEmptyObject(obj) {
        for(var prop in obj) {
            if(obj.hasOwnProperty(prop))
                return false;
        }
        return JSON.stringify(obj) === JSON.stringify({});
    }