1. Case study: Record baseline - solution implementation

    Article: AN0002414Updated: 09.03.2020

    Creating class copy

    We will record actual values in our original class with servers (machine). Approved values (baseline) will be recorded in a new class. We will create this new class easily by copying the class in the detail of class Machine. This function will create a class of the same structure but without data. Rules and buttons are disabled upon creating the class copy which is ok with us. If we are copying class that has a parent class, which is the case of the machine class, the new class does not have any parent class set up. If we wanted to store in the baseline also values from the parent class columns, we would copy also this parent class and linked the both copied classes into the hierarchy. However, parent class of the machine class is the class Configuration items which does not have any columns that are important from the baseline point of view so we do not have to do that.

    The copied class has a new code that we will modify to e.g. machine_baseline. We will also change the class name in order we distinguish it from the original class with servers.

    In order that we know which record from baseline belongs to which record in the class with servers, we will create in the class baseline column id-original and will store in it Id of the class machine.

    Button for copying values to the class baseline

    Data should be copied into the class baseline after server administrator clicks on the button. Therefore, we have to create a button in the class with servers that we will display in the record detail and we will set its visibility to the role System administrator. The button will not be displayed to roles that have access to the class servers but should not be able to define the baseline. We will use button of type Script. We could place the whole code into this button but for the sake of reuseability we will split it into two parts.

    Code in the button Script:

    //#block baseline

    //save the record
    if ( OGDataDetailForm.Save(false))
    CreateCopyToBaseline( OGModel, OGDataParent, OGActualDataRowId, 'machine_baseline');
    OGForm.SetSuccess(OG.MessageLoc.GetText(OGModel.Id, 'ci_baseline_update'));

    We are stating script block baseline at the beginning of the script, which we will use further in the script. After clicking on the button we will call function CreateCopyToBaseline and display a message about successful baseline update. We will not hard-code the message in the script but we will create a special object for it (menu Administration - Localization - Message). The advantage of this approach will be that we will be able to use the same message with various classes, we will be able to update it in a single place and the user will be displayed the language version according to the user settings.

    We will define the function CreateCopyToBaseline in the block script. If we have more classes in which we will maintain baseline, we can use in each of the them the above stated simple code in the button and the logic of baseline copying itself can be separated and defined just once.

    Code in the block script baseline (there is also function CheckDifferentData, which we will use later on, defined in the block):

    function CreateCopyToBaseline( model, dataParent, actualDataRowId, classDefBLCode)
    //Script copies value from the record in the class dataParent into the corresponding record in the class classDefBLCode
    var clBL = model.ClassDefs[classDefBLCode];
    var dr = OG.DataRow.GetDataById(dataParent.Id, actualDataRowId);

    //read actual record from the baseline or create a new one
    var f = OG.DataRow.GetDataRowFilter(clBL.Id);
    f['id-original'] = actualDataRowId;
    var drBLl = OG.DataRow.GetDataByFilter(f);
    var drBL = null;
    if ( drBLl.Count == 0)
    drBL = OG.DataRow.CreateNew(clBL.Id);
    drBL['id-original'] = dr.Id;
    drBL = drBLl[0];

    //copy columns
    var colIds = clBL.Columns.GetIds();
    for( var i = 0; i < colIds.Count; ++i)
    //find columns in both classes
    var colBL = clBL.Columns.GetById(colIds[i]);
    var col = dataParent.Columns[colBL.Code];

    //only if there are both columns and they are not system columns
    if ( col != null && colBL != null && !col.IsSystemColumn)
    drBL[colBL.Id] = dr[col];


    function CheckDifferentData(columnCode, actualDataRow, dataRows)
    //only for the current record
    if ( actualDataRow['sourcetype'] == 1 && dataRows.Count == 2)
    //find another record
    var drBS = ( dataRows[0]['sourcetype'] == 1 ? dataRows[1] : dataRows[0]);

    //compare column values
    return actualDataRow[columnCode] != drBS[columnCode];

    return false;

    Query comparing actual values in the class machine and approved values in the class baseline

    We are selecting values from both classes in the query. We have record type (actual values or  baseline), link to the record in the class machine and columns that are concerned from the baseline point of view in the clause SELECT.

    SELECT 1 AS SourceType, 'cs-CZ::Stávající~en-US::Actual' AS Source, m.id AS machine___machine, m.[virtual-server-cpu] AS CPU, m.[virtual-server-ram] AS RAM, m.vcpu AS vCPU, m.vram AS vRAM, pc.name AS Patch_category, m.[dhcp-reservation] AS DHCP_reservation, m2.name AS Current_node, m3.name AS Failover_node
    FROM {{:class.machine:}} m
    LEFT JOIN {{:class.machine:}} m2 ON m.[current-node] = m2.id
    LEFT JOIN {{:class.machine:}} m3 ON m.[failover-node] = m3.id
    LEFT JOIN {{:class.patch-category:}} pc ON m.[patch-category] = pc.id
    WHERE m.deleted IS NULL


    SELECT 2, 'cs-CZ::Baseline~en-US::Baseline', mb.[id-original], mb.[virtual-server-cpu], mb.[virtual-server-ram], mb.vcpu, mb.vram, pc.name, mb.[dhcp-reservation], m2.name, m3.name
    FROM {{:class.machine_baseline:}} mb
    LEFT JOIN {{:class.machine:}} m2 ON mb.[current-node] = m2.id
    LEFT JOIN {{:class.machine:}} m3 ON mb.[failover-node] = m3.id
    LEFT JOIN {{:class.patch-category:}} pc ON mb.[patch-category] = pc.id
    WHERE mb.deleted IS NULL<-code>

    Master-detail relation in the class Machine

    We will create master-detail relation in the query from the previous point. We will display record type (Actual / Baseline) and values in fields that we want to monitor.

    Formatting rules in the query

    We will create a corresponding rule of type Set formatting - Before displaying record in the list for each column that we are monitoring in the baseline. The below stated rule is an example used for column cpu. We will state in the rule in the Script for condition the following code (script block and function that we call).

    //#block baseline
    //CheckDifferentData('cpu', OGActualDataRow, OGDataRows);

      //just for current record
      if ( OGActualDataRow['sourcetype'] == 1 && OGDataRows.Count == 2)
        //find the second record
        var drBS = ( OGDataRows[0]['sourcetype'] == 1 ? OGDataRows[1] : OGDataRows[0]);

        //compare values in columns
        var b = OGActualDataRow['cpu'] != drBS['cpu'];
        if (b)
          //Create and save object baselineChange with a property set to true
          OG.SetItem( 'baselineChange', true);

        return b;

      return false;

    We will also define in the rule e.g. background color that will be applied if the condition is met.

    Message text

    We will create again a message in menu Administration - Localization - Message.

    Script displaying the message in the class

    In the script we are building on the fact that the rule Before displaying the record runs before the event OnPrerender. Therefore, we can create an object baselineChange in the rule by means of function OG.GetItem, that we will insert into the page, and then we will read it in the class detail by means of method OG.SetItem. If the object has value true, we will display the message.

    function OnPreRender()
      //check whether there is a deviation from baseline
      if (OG.GetItem( 'baselineChange'))
        OGForm.SetInfo(OG.MessageLoc.GetText(OGModel.Id, 'ci_baseline_deviation'));