A 360 fokos értékelő rendszer

Az újítások egyikeként alakítottuk ki az értékelő rendszerünket, ahol a cég alkalmazottai 3 havonta értékelhetik kollégáik munkáját, kommunikációját. Emellett leírhatja, ha valamilyen problémája vagy észrevétele van. Mindezt anonimitás mellett vagy akár névvel is. A visszajelzések tisztázzák a cégen belüli félreértéseket, javítja a kommunikációt és a csapat integritását, valamint értékeli az egyénileg hozzátett munkákat.

Ezt az első körökben a G-Suite rendszerében alakítottuk ki, majd fél évvel később létrehoztuk saját fejlesztésű Horizont rendszerünket. A cikk további részében értelemszerűen az előbbit szeretném bemutatni.

A 360 fokos rendszer hátteréről, kitöltendő kérdéseiről és kidolgozottságáról most nem szeretnék többet írni, mert a cikk szempontjából most irreleváns. Induljunk abból ki, hogy négy kategóriában hoztunk létre Google Form mintát:

  • fejlesztő
  • vezető fejlesztő
  • vezető
  • iroda

Ezek mindegyike más felépítésű Form, azonos és különböző kérdésekkel egyaránt. A fentiek valamelyikét kapja meg az adott alkalmazott aszerint, hogy melyik kategóriába tartozik.

Minden Formból egyénenként készül egy-egy másolat, amelyhez a kötődő információkat egy személyre szóló e-mailben kapják meg. Emellett pedig csatoljuk a Form publikus linkjét is. Minden alkalmazott maga küldi ki az általa kívánt csatornán a kitöltésére való felkérést a többiek felé. Szabály az volt, hogy az értékelési időszak végére mindenkinek legalább 3 kitöltéssel kell rendelkeznie a sajátján kívül. Utóbbi célja, hogy a saját magunkról kialakított képet összehasonlíthassuk a másoktól kapott visszajelzésekkel.

Megvalósítás

I. Kiinduló adatok létrehozása

1. Elsőként hozzunk létre Formokat sablonnak a kívánt kategóriák szerint. A kompetenciák felvitelére a Google Forms szerkesztő felülete tökéletes, nem kell kódból dolgoznunk.

syoc_-_360_-_form-temapltes.jpg

Pár javaslat, amire érdemes figyelni, hogy a visszaéléseket megelőzzük. Az űrlapokat az alábbiak szerint állítsuk be:

  • csak a domainen belüli felhasználók tölthessék ki
  • ne gyűjtsön e-mail címeket
  • egy ember maximum egyszer tölthesse ki

A domain ellenőrzés és anonimitás együttesét úgy kell elképzelni, hogy azonosítás kell, hogy igazoltan a domain tagja vagy, de felhasználói adatot nem csatol.

2. Készítsünk egy Spreadsheetet, ahol felsoroljuk, hogy kik vesznek részt az értékelésben.

syoc_-_360_-_alkalmazottak-listaja.jpg

A következő adatokra lesz minimálisan szükségünk:

  • e-mail cím értesítéshez
  • név a megszólításhoz, hogy személyre szóló legyen
  • melyik kategóriába tartozik, melyik Form mintát töltse ki

Az a jó, hogy egy Spreadsheetet később is könnyű lehet bővíteni, módosítani. Bár nem tagadom az igazán szép megoldás - a SYOC szellemében - az lenne ha ez G-Suite szervezetben felvett felhasználók lennének. Így ez is automatikus lenne, nem kellene karbantartani. :)

II. Script megírása

1. Kezdésnek olvassuk be az alkalmazotti listát tartalmazó Spreadsheet fájlt! Nyissuk meg és járjuk be a sorokat, amiből vegyük az első 3 oszlopot. A beolvasást a 2. indextől kezdjük (var i=2), mert a Spreadsheet a számozást 1-el kezdi és pluszban van még egy rögzített sorunk.

function listEmployee(spreadsheetId) {
  var employeeSpreadsheet = SpreadsheetApp.openById(spreadsheetId);
  var employeeSheet = employeeSpreadsheet.getSheets()[0];
  var employeeRange = employeeSheet.getRange("A:C");
  var lastRow = employeeSheet.getLastRow() + 1;
  var result = [];
  for (var i = 2; i < lastRow; i++) {
    var email = employeeRange.getCell(i, 1).getValue();
    var name = employeeRange.getCell(i, 2).getValue();
    var category = employeeRange.getCell(i, 3).getValue();
    result.push({
      email: email,
      name: name,
      category: category
    });
  }
  return result;
}

Ezzel megvannak az érintett személyek neve a megszólításhoz, e-mail címe az értesítéshez valamint, hogy melyik Form kategória lesz az övé.

2. Keressük meg az összes Form sablont az adott könyvtárból, ami valamilyen pozíciónak megfelel!

function listTemplates(sourceFolder, subFolderName) {
  var templateFolderIterator = sourceFolder.getFoldersByName(subFolderName);
  var templateFolder = templateFolderIterator.next();
  var subFolders = templateFolder.getFiles();
  var result = {};
  while (subFolders.hasNext()) {
    var file = subFolders.next();
    var mimeType = file.getMimeType();
    if (mimeType.indexOf("google-apps.form") > 0) result[file.getName()] = file;
  }
  return result;
}

3. Most, hogy a kezdő adatok megvannak menjünk végig az alkalmazottak listáján és mindenkinek generáljuk le a mappáját és benne a szükséges Formot.

var employees = listEmployee(EMPLOYEES_SPREADSHEET_ID);
var templates = listTemplates(appFolder, "Template");
// Create all form user by user
employees.forEach(function(emp) {
  if (templates.hasOwnProperty(emp.category)) {
    /* 
		A) create employee's folder and share silently
		B) generate form
		C) send email to employees
	*/
  }
});

Nézzük meg mit kell csinálni a cikluson belül.

A) Alkalmazotti könyvtár létrehozása és megosztása:

// Create user folder
var employeeFolderName = generateFolderName(emp.name, PERIOD);
var employeeFolder = periodFolder.createFolder(employeeFolderName);

// SHARE 1: share silently with employee
employeeFolder.setSharing(DriveApp.Access.PRIVATE, DriveApp.Permission.VIEW);
Drive.Permissions.insert(
  { role: "reader", type: "user", value: emp.email },
  employeeFolder.getId(),
  { sendNotificationEmails: false, supportsTeamDrives: false }
);
Logger.log("Sharing folder done.");

B) Személyre szóló Form létrehozása:

// Make copy from template
var employeeTemplateFile = templates[emp.category];
var employeeFormFilename = generateFilename(emp.name, PERIOD);
var employeeFormFile = employeeTemplateFile.makeCopy(
  employeeFormFilename,
  employeeFolder
);
Logger.log("Form copy done: " + employeeFormFilename);

// Update new form using employee's data
var employeeForm = FormApp.openById(employeeFormFile.getId());
var formTitle = generateTitle(emp.name, PERIOD);
employeeForm.setTitle(formTitle);
Logger.log("Form updated for " + emp.name);

// Make copy from template spreadsheet
var employeeFeedbackFilename = generateFeedbackFilename(emp.name, PERIOD);
var employeeFeedbackFile = templateFeedbackFile.makeCopy(
  employeeFeedbackFilename,
  employeeFolder
);
var feedbackTitle = generateFeedbackTitle(emp.name, PERIOD);
employeeFeedbackFile.setName(feedbackTitle);
Logger.log("Spreadsheet copy done: " + employeeFeedbackFilename);

// Bind spreadsheet to form
employeeForm.setDestination(
  FormApp.DestinationType.SPREADSHEET,
  employeeFeedbackFile.getId()
);
Logger.log("Spreadsheet bind to form done.");

// SHARE 2: revoke to show feedback file
Utilities.sleep(100); // wait before remove
var feedbackFileUserPermission = findPermissionForUser(
  employeeFeedbackFile.getId(),
  emp.email
);
if (feedbackFileUserPermission && feedbackFileUserPermission.role == "reader") {
  Drive.Permissions.remove(
    employeeFeedbackFile.getId(),
    feedbackFileUserPermission.id
  );
}
Logger.log("Removed viewer permission for feedback file");

C) E-mail küldése:

Ha már szépen dolgoztunk mi lenne ha formázott HTML e-mailt küldene ki a scriptünk. Lenti kód tartalmaz egy kis trükköt, amit G-Suite alatt tudunk alkalmazni. Írjuk meg a levelünket Google Docs-ban és formázzuk meg ott! Egy külső API hívással pedig alakítsuk HTML formátumúvá és azt küldjük ki.

function convertToHTML(docId){
  var forDriveScope = DriveApp.getStorageUsed(); //needed to get Drive Scope requested
  var url = "https://docs.google.com/feeds/download/documents/export/Export?id=" + docId + "&exportFormat=html";
  var param = {
    method      : "get",
    headers     : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
    muteHttpExceptions:true,
  };
  var html = UrlFetchApp.fetch(url,param).getContentText();
  return html;  
}

Még a cikluson kívül érdemes egy alkalommal beolvasni az e-mail sablont a fenti függvény alkalmazásával:

var body = DocumentApp.openById(BODY_DOCS_ID).getBody().getText();
var bodyHTML = convertToHTML(BODY_DOCS_ID);

Utána pedig szépen tudjuk alkalmazni:

Logger.log("Employee folder: " + employeeFolder.getUrl());
Logger.log("Published url: " + employeeForm.getPublishedUrl());
Logger.log("Summary url: " + employeeForm.getSummaryUrl());

var userPublishedUrl = employeeForm.shortenFormUrl(
  employeeForm.getPublishedUrl()
);
var employeeFolderUrl = employeeFolder.getUrl();

var userBody = body
  .replace(/{{PUBLISHED_URL}}/g, userPublishedUrl)
  .replace(/{{EMPLOYEE_FOLDER}}/g, employeeFolderUrl);
var userBodyHTML = bodyHTML
  .replace(/{{PUBLISHED_URL}}/g, userPublishedUrl)
  .replace(/{{EMPLOYEE_FOLDER}}/g, employeeFolderUrl);

MailApp.sendEmail({
  to: emp.email,
  subject: SUBJECT + " - " + PERIOD,
  body: userBody,
  htmlBody: userBodyHTML,
  noReply: true
});

Ezzel lényegében kész is vagyunk. Innen még érdemes lehet elkészíteni a beküldött adatok valamilyen cégre vonatkozó kiértékelését. De ezt már az olvasóra bíznám.

Hogyan lehetne továbbfejleszteni?

Aki egy kicsit is foglalkozott már szoftverfejlesztéssel az tudja, hogy egy program fejlesztését nem lehet befejezni csak abbahagyni. Itt sincs ez másképp.

Készíthetünk egy másik Scriptet, ami végig megy az összes Formon és ki vagy bekapcsolja azok kitölthetőségét. Ezzel szabályozni tudjuk mindenki számára a kitöltés lehetőségét.

Ezek után akár be is időzíthetjük. Automatikusan küldje ki 3 havonta vagy fél évente. Aztán rá 1 hétre zárja le az összes Form kitöltését. De még profibb ha zárás előtt 2 nappal még figyelmeztetünk mindenkit, hogy hamarosan lejár a kitöltési időszak.

A korábban felvetett G Suite szervezeti felhasználók átvételét is érdemes megírni.


Remélem sikerült egy érdekesebb felhasználást megmutatni. Láttunk példát Spreadsheet kezelésére, Form szerkesztésére és e-mail küldésére is. Ha szeretnél példát látni időzítésre, akkor javasolt a sorozatot tovább követni. Ha pedig maga az értékelő rendszer része fogott meg akkor ismerd meg a Horizont termékünkben rejlő lehetőségeket.