servicenow

Standardisera hur fält ska visas/döljas, låsas och göras tvingande i ServiceNow

En sak som man kan bli förvirrad av är varför fält visas, eller inte visas, vid olika tillfällen. Likaså att fält låses. Det finns olika sätt att dölja, visa, låsa och göra fält tvingande och där någonstans börjar spagettin. Att ha ett sätt att jobba med detta underlättar för förvaltning och felletning.

Sätta värde för attribut

De olika komponenter som går att använda för detta listas här:

UI Policy

Beroende på förutsättningar/värden i ett formulär så kan man bestämma följande för ett fält:

  • Mandatory

  • Visible

  • Read only

Client Script

Baserat på vissa event för själva objektet (ex. Incident) eller ändringar i formuläret kan man styra värden på attributen som man vill. Du kan bestämma värden av följande för fält (i praktiken kan du göra allt man kan göra från ett script på klientsidan här då det är vanlig scriptning man använder Client Script till):

  • Mandatory

  • Visible

  • Read only

ACL tillsammans med roller

ACL:er styr rättigheter på objekt- och fältnivå. ACL:er är kopplade till roller som är kopplade till grupper vars gruppmedlemmar får rollerna och rättigheterna som är kopplade till rollerna. Det man kan styra via ACL är:

  • Visible

  • Read only

Dictionary

Den inställning du gör här påverkar all användning av relaterat fält. Inställningar du gör här kan du inte åsidosätta någon annanstans. Följande kan du bestämma här:

  • Mandatory

  • Read only

UI Action

Rent krasst skulle man kunna använda UI Action för att göra ändringar i formuläret på samma sätt som ett Client Script gör. Dock är det att föredra att använda, exempelvis, UI Policy för att påverka fälten då UI Action agerar på en händelse. Framför allt är det bra om vi kan se till att vi använder så få sätt som möjligt och då kanske denna inte behövs. Med det sagt kan man påverka fält på samma sätt som för Client Script:

  • Mandatory

  • Visible

  • Read only

Standardisera

Ok, hur gör vi nu då. Ett förslag är att använda de olika funktionerna ovan på detta sätt, i prioritetsordning uppifrån:

UI Policy Använd UI Policy som först alternativ för att bestämma om ett fält ska vara Mandatory, Read only eller Visible. Det går att göra samma saker med Client Script men enligt ServiceNows dokumentation sparar man prestanda på att använda UI Policy. Det är möjligt att den prestandaskillnaden inte är märkbar men det är lättare att konfigurera dessa attribut i UI Policy än att programmera dessa i Client Script. M.a.o., går det att ställa in dessa attribut i UI Policy, gör det, utan undantag. Eller, ok, ett undantag, det är när vi använder ACL:er och roller istället. Läs där nere (det skulle kunna gå att lösa detta med scriptdelen i UI Policy men vi försöker undvika script om det går). Ska policyn användas i alla ärvande tabeller så kan man definiera policyn i tabellen som ärvs och markera Inherit (Advanced view) så ärvs policyn till alla ärvande tabeller.

ACL och roller För att styra vilka rättigheter olika roller har använder vi ACL:er tillsammans med roller. Du kan läsa här om hur man sätter upp en sådan struktur för tabeller/objekt som kommer med ServiceNow. Har du skapat en egen tabell så behöver du sätta upp reglerna som ACL:er.

  • Sätt förs upp rättigheterna för tabellen (<tabell>.

  • Sätt sedan upp en generell regel för alla fält (<tabell>.*). Båda dessa ska ge relaterade roller rätt att se objekt och fält.

  • För att dölja fält, sätt upp regler för specifika fält och definiera roll som har rätt att se det fältet (<tabell>.<fält>). På ovan länk har jag ett exempel på detta.

Notera att när man skapar en ny tabell så läggs följande regler automatisk till, allt annat (som exempelvis <tabell>.*) måste läggas till. ServiceNow har även skapat en ny roll och kopplat den till reglerna:

  • <tabell> | delete

  • <tabell> | write

  • <tabell> | create

  • <tabell> | read

Dictionary Enda anledningen till att sätta attribut härifrån är om man vet att fältet alltid ska vara antingen Mandatory eller Read only oavsett var det används. Fast du kan troligtvis använda UI Policy till detta, tillsammans med arv (om det finns tabeller som ärver varandra) enligt ovan.

Client script Använd helst inte detta för att styra nämnda attribut, använd UI Policy istället. Det kanske finns tillfällen när UI Policy inte går att använda utan man måste använda Client Script istället. Kommer ni på något sådant, skriv gärna i kommentaren nedan.

UI Action Liksom för Client script, undvik att använda UI Action om det absolut inte går m.h.a. något ovan. Jag kan inte komma på något tillfälle när man måste använda UI Action istället för UI Policy.

Applicera standarden

Det finns speciellt två scenarior när man ska använda denna standard. Ena är när man behöver sätta värdet på något attribut/fält och det andra är när man måste hitta var attribut/fält sätts.

Om man har som princip att alla alltid börjar uppifrån i listan under “Standard” ovan och använder det sätt som först är en gångbar lösning, då bör det vara mycket lättare att även hitta vad det är som sätter attributet. Börja alltid högst upp även när du letar efter hur värdet sätts.

Och om du fortfarande inte hittar var saker styrs kanske du kan titta om det finns någon Business rule som gör något (finns exempel när Business rule tar bort incidenter för visning. En sådan regel kan, och gör ibland, titta på vad användaren har för roll och ser till att inte visa några, exempelvis, incidenter för användaren).

Fortsätt läsa

Standardisera återanvändning av JavaScript i ServiceNow

Det blir normalt sett en del scriptande i ServiceNow. Smart grej är att tänkta på att designa koden så den går att återanvända. Då slipper man själv, och kollegorna, att utveckla samma funktion flera gånger och man spar tid. Man måste så klart ha ett arbetssätt så kollegorna vet om vad det finns för funktion att tillgå utan att man måste hålla på med för mycket dokumentering. Har man ett standardiserat sätt att jobba med återanvändning så ska det vara lätt att hitta funktion som har blivit utvecklad i systemet.

Allt i denna artikel är gjort i Orlando.

Naturliga ställen för att skapa kod för återanvändning är i Script include på serversidan och UI Script på klientsidan. Vi skapar klasser i vilka vi kan ha funktioner som vi når från övriga script på server- respektive klientsidan. Det är praktiskt om vi kan använda oss av abstrakta metoder så vi slipper skapa objekt innan användning. Det kan gå, men inte alltid. Exempelvis måste vi skapa objekt om vi vill att objektet ska ha kunna hålla på värden mellan anrop till klassens funktioner.

Det var ju enkelt, då gör vi så. Var det allt? Nja, vi kanske måste kolla på hur vi dokumenterar detta. Bra kod dokumenterar sig själv säger man och man räknar med kommentarer i texten till det så är det helt sant. Helst skulle man ju vilja generera JSDoc från koden man gjort men det tittar vi inte på nu. Sen måste vi titta lite på hur vi skapar klasserna också. Vill vi ha abstrakta metoder? Inte lika rättframt som för Java även om det är besläktade språk.

Namnstandard

För att vara säkra på att de klasser vi skapar inte kolliderar med de klasser som redan finns i ServiceNow behöver vi en namnstandard.

<prefix><objekt>Utils

Prefix är 1-2 bokstäver som gör att klassen skiljer sig om vi råka skapa en klass för ett objekt där ServiceNow redan har skapat en klass.

Objekt är den, troligtvis, tabell som ska användas. Kan vara andra entiteter också.

Utils är bara en statisk text som visar att det är någon form av verktygs- eller hjälpklass.

Exempel:

scIncidentUtils

‘sc’ står i det här fallet för SMICloud (mitt företag). ‘incident’ för att klassen ska användas i incidentsammanhang. Hade vi inte lagt till ‘sc’ i början hade vi fått krock med incidentUtils, på serversidan, som kommer med ServiceNow.

Klass

Vi skapar klasser i UI Script för klientsidan och Script Includes för serversidan. Vi jobbar med klasser för det är välstrukturerat sätt att kapsla in funktioner relaterade till samma område. Vi kommer kunna anropa en funktion genom att antingen anropa den direkt via klassen (abstract) eller skapa ett objekt och använda det för att anropa funktioner. Här kommer du behöva ta ett beslut, behöver du att klassen bär på data som du ska använda senare i scriptet måste du skapa ett objekt. Behöver du använda en funktion bara för att göra ett anrop och sen behöver du inte klassen längre, du kan du anropa funktionen utan att skapa ett objekt. Kallas abstrakt funktion normalt sett (oklart om det är rätt term i det här fallet, men sättet är iaf likt).

Exempel

Låt säga att en incident inte ska kunna relatera till ett problem som har lägre prioritet än själva incidenten. Vi gör jämförelsen när vi försöker spara när incidenten fått ett nytt relaterat problem eller när incidentens prioritet ändras. Är problemets prioritet lägre än incidentens så ska inte uppdateringen sparas och ett meddelande skrivas i formuläret. I en komplett lösning måste man även implementera kontroller på problemsidan men vi utgår bara från incidentsidan i detta exempel.

Vi skapar först ett Script include som göra själva jämförelsen (Jag har inte använt mig av fellhanteringen jag pratar om här för att göra koden så ren som möjligt. Lägg gärna till den i skarpa fall.):

/** @class      scIncidentUtil      Utility class incidents */
var scIncidentUtil = Class.create();
scIncidentUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    /** Compare two objects' priorities. The both input parameters/objects must to have a field 'priority' of the same format. 
    *@param     reference1      The first reference object that the priority should be compared with.  
    *                       reference2      The second reference object that the priority should be compared with. 
    **/
    comparePriorities: function(reference1, reference2) {
            //Compare using normal javascript functionality and return. Exactly what should happen is not decided here since that would minimize the general use of this function
            return reference1.priority.localeCompare(reference2.priority);

    },

    type: 'scIncidentUtil'
});

Notera att denna Script include är det API vi skapat och i vilken vi ska fylla på med funktioner relaterat till incidenter. Skapa en annan Script include för övriga tabeller/objekt.

Vi skapar sen en Business rule från vilken vi använder vår util-klass och funktion. Sätt följande värden:

  • Markera Advanced då vi måste scripta.

  • When sätts till before (om vi måste göra ‘abort’ så får inget sparas innan.

  • Business rule används när vi skapar och uppdaterar incidenten så markera Insert och Update.

  • Kör regeln endast om man väljer nytt relaterat problem till incidenten eller när incidentens prioritet uppdateras. Sätts i Filter Condition.

Business rule

Business rule


Och så klipper vi in detta scrip under flik Advanced:

(function executeRule(current, previous /*null when async*/) {

    //In case problem has a lower priority than the incident, abort and inform the user.
    var comparison = scIncidentUtil.prototype.comparePriorities(current, current.problem_id);
    if(comparison == -1){
        gs.addErrorMessage("Incident cannot have higher priority than related problem. Update not saved.");
    }
    
})(current, previous);

När vi nu uppdaterar incidentens prioritet eller relaterade problem görs kontrollen. Om incidentens prioritet är lägre eller lika med problemets prioritet så händer inget. Annars skrivs ett meddelande ut i formuläret:

Felmeddelande i incidentformuläret

Felmeddelande i incidentformuläret

Ett alternativ till ovan lösning hade kunna vara att lägga den på task-nivå. Då hade man kunnat använda den för objekt som ärver task-tabellen också. I detta scenario så sker jämförelsen på ett lite annat sätt på problemsidan så kanske är det bra att skapa en utility-klass för problem där man itererar igenom alla relaterade incidenter och gör en jämförelse. Ingenting hindrar dig från att göra själva iterationen i en utility-klass för problem och göra de enskilda jämförelserna m.h.a. denna utility-klass för incident (även om det kanske är lite overkill för en sådan enkel jämförelse).

Fortsätt läsa

Standardisera hur rättigheter används i ServiceNow

Har du någon gång suttit och slitit ditt hår när du försökt förstå hur accessreglerna fungerar då du sitter och letar efter fel? Jag har… Det går ju att debugga ACL-reglerna rätt bra men för att göra det lättare att förvalta plattformen kan det vara bra att man har definierat en struktur för tilldelning av rättigheter som alla förstår och använder. Detta går nog att göra på olika sätt men här är ett förslag hur man kan jobba med att tilldela rättigheter för de som ska kunna se olika saker samt de som ska kunna jobba med olika saker i ServiceNow.

Allt i denna artikel är gjort i Orlando.

När vi jobbar med rättigheter har vi lite olika komponenter att tillgå i ServiceNow:

Access Control Rules (ACL)

Själva reglerna som definierar access till olika tabeller och fält

Roller

ACL:er kan kopplas till roller som används antingen av grupper eller läggs direkt på personer (vilket man inte ska göra).

Grupper

En grupp kan kopplas till en roll som har vissa rättigheter (d.v.s. associerade till ACL-regler). Medlemmar i en grupp får relaterade rättigheter. Det är endast på detta sätt man ska tilldela rättigheter till personer. Inga rättigheter ska läggas direkt på en person även om det rent tekniskt är möjligt. Genom att koppla rättigheter/roller till gruppen kan man göra justeringar i gruppen som slår på alla medlemmar. Det är även lätt att ge och ta bort rättigheter från personer.

Skapa och uppdatera ACL:er

För att kunna skapa och uppdatera ACL:er som diskuteras i denna artikel måste admin få rättigheter att göra det. Annars kan han/hon endast läsa dessa.

1. Klicka på “System Administrator” (eller den användare som du har i den listan):

Skärmavbild 2020-11-03 kl. 12.41.02.png

2. Markera “security_admin” och klicka “Ok”. Nu kan du uppdatera ACL:er.

Skärmavbild 2020-11-03 kl. 12.41.10.png

Rätt att se ärendetyp

Låt oss säga att vi har en användargrupp som ska kunna se incidenter. Samma princip gäller oavsett vilket objekt (ex. change, problem) det är i ServiceNow.

(1) Vi skapar en grupp som heter “AG View Incident”. ‘AG’ står för Access Group. I.o.m. att detta inte är en grupp som ska vara synligt, exempelvis kunna tilldela incidenter till, gör det inget att namnet har prefix AG.

(2) För objekt som kommer OOB så finns det redan roller vi kan använda. Skulle vi sätta upp egna roller så måste vi gå in och göra ändringar på funktionalitet som kommer med plattformen och det ska vi inte göra. För att se incidenter krävs det att man har rollen ‘sn_incident_read’.

(3) Det krävs två ACL:er för att en användare ska kunna läsa, men inte skapa eller skriva, incidenter. Följande krävs (vi behöver inte skapa dessa med undantag om vi vill visa fält som är dolda enligt nedan):

incident Ger access till tabellen

incident.* Ger access till fälten i incident.

incident.<field> Styr rättigheterna för specifika fält. Exempelvis finns en regel för incident.work_notes som ger roll ‘sn_incident_write’ access att läsa Work Notes. I.o.m. att det bara finns en läsregel för det fältet och roll ‘sn_incident_read’ inte finns med så kommer användare som inte har roll ‘sn_incident_write’ se Work Notes. Vill du att användaren ska kunna se Work Notes kan du skapa en ACL där du ger ‘sn_incident_read’ rätt att se det fältet. Gör ‘Insert and stay’ på work_note-regeln och lägg till roll till ‘sn_incident_read’ (roll-listan kopieras inte när man gör ‘Insert and stay’).

Notera att det finns en Business Rule (‘incident query’) som säkerställer att man har rätt roll för att se incidenter. Detta är en av anledningarna till att vi inte vill skapa egna ACL:er och roller utan använda vad som finns. Skulle vi skapa egna ACL:er och roller så skulle vi behöva justera funktionen i denna affärsregel.

Skapar man egna objekt/tabeller behöver man skapa egna ACL:er och koppla dessa till roller.

(4) Vi behöver koppla roll ‘sn_incident_read’ till ‘AG View Incident’. Gå till gruppen och lägg till roll ‘view_incident’ i den relaterade listan längst ner.

(5) Ta en användare som inte redan har rättigheter att se incidenter och lägg till den personen i ‘AG View Incident’.

(6) Impersonate ovan användare. Skriv in ‘incident.list’ i vänstermenyns sökfält och tryck enter. En lista med incidenter ska visas. Om du klickar på ena öppnas incidenten med alla fält låsta.

Hemliga/Säkerhetsklassade fält

Låt säga att vi lägger ut ett fält för personnummer i incidentformuläret. Det ska inte kunna ses av alla utan endast av personer med rätt behörighet. Låt oss kalla fältet för “Social number” (u_social_number).

(1) Skapa och lägg ut fältet som första steg. Impersonate samma person som tidigare och kolla att du kan se fältet.

(2) Skapa en regel, incident.u_social_number, specifikt för detta fält. Vi tilldelar rollen security_admin till denna ACL. Så här ser regeln ut:

Skärmavbild 2020-10-29 kl. 17.28.45.png

(3) Impersonate samma användare som tidigare. Lista incidenter och öppna en incident. Du ska nu inte kunna se fältet ‘Social Number’ men alla andra. Om Security Admin loggar in så kommer den personen se fältet.

Rätt att arbeta med ärende

Vi fortsätter att jobba med incidentformuläret. Nu behöver vi ha möjlighet att skapa grupper för de som faktiskt ska jobba med incidenter också. Det är folk i olika supportlinjer i organisationen.

(1) Skapa en grupp som heter ‘2nd Line’.

(2) Vi måste lägga ut ett fält i gruppformuläret som heter ‘Type’. Gör det (m.h.a. Form Design, fältet finns fördefinierat).

(3) Vi måste skapa en typ för vårt ändamål. Skriv in ‘sys_user_group_type.list’ i sökfältet ovanför vänstermenyn. Tryck enter.

(4) Skapa en ny typ som heter ‘incident_agent’.

(5) Gå tillbaka till grupp ‘2nd Line’ och välj den nya typen i fältet du lagt ut.

(6) Lägg in en medlem i gruppen som inte är samma som fick läsrättigheter ovan och som inte har rätt att jobba med incidenter.

(7) Öppna en incident. Högertryck på texten ‘Assignment group’ och välj Dictionary.

(8) Vi måste nu se till att grupp med typ ‘incident_agent’ går att välja. Det är det som står i fliken ‘Reference Specification’ som bestämmer vilka grupper man kan välja. Då fält ‘Assigned to’ är ärvt från task-tabellen så kommer alla ändringar vi gör här slå på alla andra formulär som också använder det fältet. Det måste vi undvika och för det ändamålet så ska vi göra ‘Override’ på reference qualifier. Med ServiceNow kommer ‘itil’ definierat som ‘Type’ för assigned to.

I den relaterade listan ‘Dictionary override’ finns en rad med tabell ‘incident’. Öppna den. Det finns ett fält som heter ‘Override reference qualifier’ och när du markerar den visas ett textfält som heter ‘Reference qualifier’. Här ska du skriva in referensen till den grupptyp du vill ska vara valbar. Skriv in följande men ändra sys_id till den grupptyp du definierat i systemet:

typeLIKE244696dbdb04241014c5808768961910

Du kan få fram sys_id genom att gå till typen du skapade ovan och välja att kopiera sys_id.

(9) Spara och gå tillbaka incidenten. Klicka på förstoringsglaset bredvid fältet ‘Assignment group’. Fönstret som visas ska se ut så här:

Skärmavbild 2020-10-29 kl. 17.45.32.png

Skulle du göra samma sak på ett problem eller change så skulle du få en annan lista innan du gjort anpassningarna i denna artikel. Notera att på change måste du göra en override på “Attributes” och skriva in tree_picker=false också. Av någon anledning fungerar detta inte om den är satt till true (den sätts direkt i Dictionary till true i relaterade listan ‘Attributes’).

Nu kan man ju tro att det är klart men det är det ju inte. Om du gör impersonate på den användare du la till i gruppen så kommer den personen inte se några incidenter, vi måste fixa lite rättigheter.

(10) Vi kan återanvända samma struktur som för accessgruppen vars medlemmar skulle kunna läsa incidenter med skillnaden att vi nu måste tilldela rättigheter för att kunna skriva också. Gå till grupp “2nd Line”.

(11) En grupp kan ingå i en annan grupp. Så istället för att tilldela ‘2nd Line’ en roll så behåller vi strukturen med accessgrupper. M.a.o. skapar vi en grupp ‘AG Write Incident’ och kopplar roll ‘sn_incident_write’ till den. Vi kopplar ihop grupperna genom att välja grupp ‘AG Write Incident’ som ‘Parent’ till ‘2nd Line’. Nu räcker det med att vi lägger till användare i grupp ‘2nd Line’ för att de ska få rättigheter att jobba med incidenter. På det sättet förlitar vi oss på samma sätt att jobba med rättigheter (d.v.s. accessgrupper) men behöver endast administrera användare för den grupp som de som ska jobba med incidenter är med i.

Om det finns någon som ska kunna uppdatera incidenter utan att vara tilldelad lägger man den personen direkt i accessgruppen (exempelvis om en person är på semester kanske dennes chef ska kunna tilldela den personens ärenden till någon annan). Rollen ‘sn_incident_read’ ingår i gruppen ‘sn_incident_write’ så den får användaren på köpet.

Gör du impersonate på personen som är med i ‘2nd Line’ så kommer personen kunna se och arbeta med incidenter.

Det finns begränsningar med ovan sätt att arbeta. Du kan ju bara ha en ‘Parent’ till en grupp. Är det så att de som är med i ‘2nd Line’ ska kunna jobba med flera ärendetyper (change, problem, etc.) så funkar det inte. Då är det bättre att man helt separerar accessgrupper och grupper för tilldelning. Då måste man lägga varje användare som ska jobba med, exempelvis, incidenter i en accessgrupp (‘AG Write Incident’) samt i en grupp som används för tilldelning (‘2nd Line’). Ingen ‘Parent’ väljs då i någon grupp. Detta är troligtvis det sätt som kommer vara vanligast.

(13) Nu så, nu kan du välja vilka som ska kunna jobba med incidenten också.

Det finns fördefinierade roller för andra objekt OOB. Det är bara att använda dessa för relaterade objekt på samma sätt som är beskriver i denna artikel Listar de vanligaste här:

Skärmavbild 2020-10-30 kl. 10.05.06.png

Felhantering av script i ServiceNow

För långsiktig, och varför inte kortsiktig, stabilitet i ServiceNow bör man ha en bra felhantering i scripten man utvecklar. Här beskriver jag hur man kan skapa ett standardiserat sätt för detta i plattformen för att inte behöva återskapa hjulet hela tiden samt för att alla ska ha samma sätt att arbeta på.

Allt i denna artikel är gjort i Orlando.

Lösningen består av följande:

  • En Script Include

  • Ett UI Script

  • Ett Scheduled Script

Exempel för användning från både klientsida och serversida är beskrivna nedan.

I Script Include och UI Script finns javaklasser/API definierade som används för att på ett standardiserat sätt logga fel som uppstår i script man utvecklat. Denna loggning hamnar i error logg (‘syslog’).

För att man inte ska behöva gå in och titta varje dag om det kommit några nya fel så skapar man ett schemalagt script som kollar om det dykt upp några nya fel. Har det gjort det skapas en incident med information om de fel som uppstått och läggs på rätt CI och rätt grupp.

Lösningen är gjord på det sätt att det inte är någon skillnad på hur man använder API:t på klient eller serversidan.

Format på information i error log

Outputen i loggen kan exempelvis se ut så här:

[ERROR][Error type: RangeError][Script type: Client Script][Script: Change Assigned To][Function: onChange][Other info: n/a][Thrown error message: toPrecision() argument must be between 1 and 100]

Beskrivning i den ordning de loggas:

[<nivå>] 		[ERROR] 		 

INFO, WARNING, ERROR | Notera att man anger manuellt detta värde i koden och det sker ingen validering.

[<feltyp>] 		[RangeError]

Här skrivs felet in som man hämtar från objekt “Error” som kastas av systemet.

[<scripttyp>] 		[Client Script]

Vilken typ av script det är så man lätt ska hitta det i systemet

[<namn>] 		[Change Assigned To]

Namnet på det script, affärsregel, etc. så man lätt ska hitta var felet kastades.

[<funktionsnamn>] 	[onChange]

Namnet på den funktion i vilken felet kastades.

[<övrig info>] 	[xxx]

Övrig information som man vill logga hamnar i det här fältet. Detta används troligtvis om man har fel som är återkommande och man behöver mer information vid debugging.

[<info om fel>] 	[Thrown error message: toPrecision() argument must be between 1 and 100]

Felmeddelande som systemet skickar med i det felobjekt som kastas.

Script Include

Först börjar vi titta på den Script Include som anropas från något script på serversidan eller från lösningens UI Script.

Som du ser i bilden nedan så jobbar vi i globala scopet då detta är något som ska kunna användas i hela plattformen. Den måste också vara anropbar från klienten.

Script Include scLogginUtil

Script Include scLogginUtil

Koden nedan kan du kopiera in i en script include som du döper till “scLoggingUtil”. I övrigt har jag dokumenterat vad som händer i koden i scriptet.

Vill du läsa mer om script includes och GlideAjax kan du läs här:

Script Include

GlideAjax

Script Include scLoggingUtil:

/** @class      scLogUtil       Utility class for handling logging */
var scLoggingUtil = Class.create();
scLoggingUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    /** Handles logging requests from the client*/
    logClientError: function() {
        try {
            
            //We need the data from the client to be put in the same ojbect structure as if the data was coming from a server side script and also mapped to some of the data coming with a thrown error (e.g. .name and .message). Do note that since a thrown error is an object we use object here instead of array.
            var parameters = {};
            parameters.name = this.getParameter('sysparm_error_name');
            parameters.level = this.getParameter('sysparm_level');
            parameters.typeOfScript = this.getParameter('sysparm_type_of_script');
            parameters.nameOfScript = this.getParameter('sysparm_name_of_script');
            parameters.nameOfFunction = this.getParameter('sysparm_name_of_function');
            parameters.otherInfo = this.getParameter('sysparm_other_info');
            parameters.message = this.getParameter('sysparm_message');

            this.logError(parameters);

            return true;

        } catch (err) { //In the unlikely event that something goes wrong in logClitenError(), we do a print to the error log directly from here

            gs.logError("[ERROR][" + err.name + "][Script include][scLoggingUtil][logClientError][" + err.message + "]");
            return false;

        }
    },

    /**
     *Print the error to the error log
     *@param        err     An object containing a defined set of information about the error. 
     *                      err.level: INFO, WARNING, ERROR. 
     *                      err.name: Type of error caught by a thrown error or defined manually. Preferable the first on if possible. 
     *                      err.typeOfScript: Client Script, Script Include, Business Rule, etc. 
     *                      err.nameOfScript: The actually name of the entitey where the script retains. 
     *                      err.nameOfFunction: A script can contain multiple functions. This defines the function where the error was thrown. 
     *                      err.otherInfo: In case there is a need, mainly for debuggin purpose, to post additional information to the error log, this information can be added to otherInfo.
     *                      err.message: The message thrown by the error. 
     */
    logError: function(err) {

        //The actually writing to the error log
        gs.logError("[" + err.level + "]" + 
                    "[Error type: " + err.name + "]" + 
                    "[Script type: " + err.typeOfScript + "]" + 
                    "[Script: " + err.nameOfScript + "]" + 
                    "[Function: " + err.nameOfFunction + "]" +
                    "[Other info: " + err.otherInfo + "]" + 
                    "[Thrown error message: " + err.message + "]");

    },

    type: 'scLoggingUtil'

});

UI Script

För att kunna använda denna funktion från klienten måste vi också ha ett UI Script som anropar ovan Script Include. Klienten anropar funktion logClientError() (om man anropar från script på serversida anropar man logError() direkt). Detta är inbakat i API:t och inget vi egentligen behöver tänka på när vi använder API:t.

UI Script ska vara globalt då det ska kunna nås från hela plattformen. I detta fall har jag bara brytt mig om Desktop som UI Type.

UI Script scLoggingUtil

UI Script scLoggingUtil

Koden nedan kan du kopiera in i ett UI Script som du döper till “scLoggingUtil”. I övrigt har jag dokumenterat vad som händer i koden i scriptet.

Vill du läsa mer om UI Script kan du gör det här:

UI Script

/** @class      scLogUtil       Utility class for handling logging */
var scLoggingUtil = Class.create();

scLoggingUtil.prototype = {

    initialize: function(){

    },

    /**Prepares the error data and sends it to the server
    *@param             level           INFO, WARNING, ERROR
    *@param             err             Thrown error object
    *@param             typeOfScript    The type of script where the error was thrown
    *@param             nameOfScript    The name of the script where the error was thrown
    *@param             nameOfFunction  Name of the function where the error was thrown 
    *@param             otherInfo       If additional information is needed, mainly for debuggin purpose, that information can be added here. 
    *
    */
    logError: function(err, level, typeOfScript, nameOfScript, nameOfFunction, otherInfo) {
        try{
            var ga = new GlideAjax('scLoggingUtil');        
            ga.addParam('sysparm_name', 'logClientError');
            ga.addParam('sysparm_message', err.message);
            ga.addParam('sysparm_error_name', err.name);
            ga.addParam('sysparm_type_of_script', typeOfScript);
            ga.addParam('sysparm_name_of_script', nameOfScript);
            ga.addParam('sysparm_name_of_function', nameOfFunction);
            ga.addParam('sysparm_other_info', otherInfo);

            ga.addParam('sysparm_level', level);
            ga.getXML(responseHandler);

        }catch(err){//In the unlikely event that something goes wrong in above function, we do a print to the concole  directly from here

            jslog("[ERROR]" + 
                  "[Error type: " + err.name + "]" + 
                  "[Script type: UI Script]" + 
                  "[Script: scLoggingUtil]" + 
                  "[Function: logError->catch]" +
                  "[Thrown error message: " + err.message + "]");

        }
        /**This callback function handles the response from the server. If everyhting works fine we do nothing. If something goes wrong on the server side (and answer is false) we print an error message to the console. 
        */
        function responseHandler(response) {
            var answer = response.responseXML.documentElement.getAttribute("answer");

            //Do note that the result is returned as a string, not a bool
            if(answer == "false"){
                jslog("[ERROR]" + 
                      "[Error type: Problem logging error in error log]" +
                      "[Script type: UI Script]" +
                      "[Script: scLogingUtil]" +
                      "[Function: logError->responseHandler]");
            }
        }
    },

    type: 'scLoggingUtil '


};

Användning från klientsidan

För att anropa scLoggingUtil från ett, exempelvis, Client Script gör man så här. “err” är det kastade felobjektet: :

var logUtil = new scLoggingUtil();      
logUtil.logError(err, "ERROR", "Client Script","Change Assigned To", "onChange", "n/a");

Du kan också anropar funktionen direkt:

scLoggingUtil.prototype.logError(err, "ERROR", "Client Script","Change Assigned To", "onChange", "n/a");


Se beskrivningen av funktion logError i UI Script scLoggingError ovan.

Om du har skapat både UI Script och Script Include enligt ovan kan du testa funktionen genom följande exempel:

1. Skapa ett Client Script för incident.

2. Sätt Type = onChange

3. Sätt Field name till Assigned to

4. Se till att den är Global

5. Kopiera in följande script och spara:

function onChange(control, oldValue, newValue, isLoading, isTemplate) {
    if (isLoading || newValue === '') {
        return;
    }

    try{
        //As an example, we trigger an error on purpose. toPrecision() cannot be 500. 
        var num = 1;
        num.toPrecision(500); 
        
    }catch(err){
        //Let's catch the error and log it
        var logUtil = new scLoggingUtil();
        logUtil.logError(err, "ERROR", "Client Script","Change Assigned To", "onChange", "n/a");

    }
}

6. Gå till en existerande incident (om den redan är öppen ladda om så att scripten laddas).

7. Ändra Assigend To. Du behöver inte spara.

8. Gå till Error Log (“Errors” i menyn).

9. Sortera så senaste “Created” är högst upp. Nu bör du se något som detta i loggen:

[ERROR][Error type: RangeError][Script type: Client Script][Script: Change Assigned To][Function: onChange][Other info: n/a][Thrown error message: toPrecision() argument must be between 1 and 100]

Användning från serversidan

För att anropa Script Include scLoggingUtil från, exempelvis, en Business Rule gör man så här. “err” är det kastade felobjektet:

var logUtil = new scLoggingUtil();
logUtil.logError(err);

Du kan också anropa funktionen direkt:

scLoggingUtil.prototype.logError(err);

Se beskrivningen av funktion logError i Script Include scLoggingError ovan.

Om du har skapat Script Include enligt ovan kan du testa funktionen genom följande exempel:

1. Skapa en Business Rule för incident. D.v.s. sätt Table = Incident

2. Välj “When” till “Before”

3. Markera endast “Update”

4. Markera “Advanced”

5. Kopiera in följande script:

(function executeRule(current, previous /*null when async*/ ) {

    try {
        //As an example, we trigger an error on purpose. toPrecision() cannot be 500. 
        var num = 1;
        num.toPrecision(500);

    } catch (err) {

        //Prepare the data to be logged
        err.typeOfScript = "Business Rule";
        err.nameOfScript = "convertNum";
        err.level = "ERROR";
        err.nameOfFunction = "executeRule";
        err.otherInfo = "n/a";

        //Access script include scLoggingUtil and log the error
        var logUtil = new scLoggingUtil();
        logUtil.logError(err);


    }

})(current, previous);

6. Öppna eller gå till incidenten du ska testa med.

7. Ändra något och spara. Ändra inte Assigned To om du inte vill trigga ytterligare fel (om du har gjort klientövningen ovan).

8. Gå till Error Log (“Errors” i menyn).

9. Sortera så senaste “Created” är högst upp. Nu bör du se något som detta:

[ERROR][Error type: RangeError][Script type: Business Rule][Script: convertNum][Function: executeRule][Other info: n/a][Thrown error message: Precision 500 out of range.]

Samla ihop felen till en incident m.h.a. Scheduled Script

Det är inte till så stor nytta att ha ett gemensamt sätt att jobba med felhantering om inte felen upptäcks och kan hanteras. Därför är det bra med ett automatiskt jobb som kollar om det dykt upp några nya fel senaste dygnet och skapar en incident med information om felen. Notera att vi skapar max en incident per dygn men alla fel listas i den incidenten. Tänk om något fel uppstår flera hundra gången under ett dygn, jag tycker inte vi behöver ha flera hundra incidenter för det felet då. Antal gånger som felet uppstått kommer ändå stå i incidenten.

1 Skapa ett Scheduled Script

2. Markera “Conditional”.

3. Kopiera in följande kod till “Condition”. Detta för att vi inte behöver köra en del kod om det inte finns några nya fel:

//Only run this if there are any new errors in the error log to collect
answer = checkNewErrors();

function checkNewErrors() {
    try {
        var gr = new GlideRecord('syslog');
        //The query is the same as in the run script. In this request, were are checking to se if there are any errors from yesterday. Depending on when you run the scheduled job you may need to change the request query. Related error messages start with '[ERROR]' in this case. 
        gr.addEncodedQuery("sys_created_onONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()^level=2^messageSTARTSWITH[ERROR]");
        gr.query();
        return gr.hasNext();
    } catch (err) {
        var logUtil = new scLoggingUtil();
        logUtil.logError(err, "ERROR", "Scheduled job", "Create incident on error", "Condition", "n/a");

    }
}

4. Kopiera in följande kod till “Run this script”:

collectErrors();

//If there are any new errors, create an incident with information about the errors
function collectErrors() {
    try {

        //In this request, were are getting errors from yesterday. Depending on when you run the scheduled job you may need to change the request query. Related error messages start with '[ERROR]' in this case. 
        var logGa = new GlideAggregate('syslog');
        logGa.addEncodedQuery("sys_created_onONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()^level=2^messageSTARTSWITH[ERROR]");
        //We aggregate and group the messages so we don't get several rows for the same type of error.
        logGa.addAggregate('count');
        logGa.orderByAggregate('count');
        logGa.groupBy('message');
        logGa.query();
        var errors = "";
        //Loop through the result and create the text for the description field containing the number of occurences of the message and the message itself.
        while (logGa.next()) {
            var messageCount = logGa.getAggregate('count');
            errors += messageCount + "::" + logGa.message + "\n";
        }


        //Create incident

        //We want to assign the incident to the support group of ServiceNow. Hence, we need to get the CI which the support group is related to. You can use the sys_id of the support group directly but if you change supportgroup for the CI ServiceNow the incorrect support group will be assigned to the incident. 
        var grCI = new GlideRecord('cmdb_ci');
        var groupAssigned = grCI.get('869e6e47939f31003b4bb095e57ffbea');

        //Create the new incident. In this case we defined Short Description, Description, CI and Assignment Group. You may want to change/add data for the new incident. E.g. if default priority is not the one you want you may define the priority as well. 
        gr = new GlideRecord('incident');
        gr.initialize();
        gr.short_description = "Errors collected from the error log";
        gr.description = errors;
        gr.cmdb_ci = "869e6e47939f31003b4bb095e57ffbea";
        if (groupAssigned) {
            gr.assignment_group = grCI.support_group;
        }

        gr.insert();

    } catch (err) {
        var logUtil = new scLoggingUtil();
        logUtil.logError(err, "ERROR", "Scheduled job", "Create incident on error", "Run this script", "n/a");
    }
}

4. Du måste ändra de sys_id som är definierade i koden så de matchar den instans du jobbar med. I raden med grCI.get är det sys_id för det CI som gäller för ServiceNow (eller dom du vill använda något annat CI). Vi behöver en Support Group som är kopplad till det CI som används annars blir det problem när incidenten ska tilldelas en grupp.

Du kan så klart definiera vilka parametrar som ska sättas till vad enligt eget önskemål.

5. Spara

6. För att test behöver vi inte definiera något schema. Klicka på “Execute Now”.

7. Gå till incidenterna. Sortera så senaste incidenten kommer högst upp.

8. När du öppnar den ska den se ut liknande denna. Antal förekomster av listat fel står längs till vänster om själva felet:

incident.png

Sen är det bara att reda ut vad som blivit fel och fixa till det.

För att starta schemaläggningen definierar du schemat i Scheduled Script. Ovan lösning letar efter fel “Yesterday”. Rimligt är att köra jobbet mellan midnatt och start av arbetsdag så man har eventuella fel i en incident när man kommer till jobbet, kanske 01:00:

Schemaläggning av Scheduled Script

Schemaläggning av Scheduled Script