Use a script to handle services schedules error

Prerequisites

git checkout origin/osp-alarms-web-configuration .
git checkout origin/osp-collections-rights-configuration .
git checkout origin/osp-scripts-configuration .

You also need a valid configuration with collections rights with the ability to use services. This example is based on this other example to setup up the configuration.

Description

The goal of this example is to create a script which goal is to look at all mandatory services and verify that they are in a valid state according to the schedules and active users of the service.

Note

The complete configuration for this example is available on the branch example-collections-script-handle-services.

git checkout origin/example-collections-script-handle-services .

Steps

1. Define a detached script with a scheduled execution

root/scripts/services/detached.scripts

{
    "moduleId": "modules.scripts.scripts-1",
    "accessedValues": [],
    "scheduledExecutions": ["0/15 * * ? * * *"],
    "sourceFile": "root/scripts/services/check_validity_services.js"
}

2. Create the actual JS script

This script validates services by following multiple steps.

Get every services

First step is to list the services we want to validate. In this example, we only care about mandatory services, but you can define the filter as you need.

When requesting a list of entries from a collection, we need to provide a page size and page number. Meaning that if there is more than page size entries that match the query, we will need to send more request while increasing the page number to get the others entries.

In this example, we request 100 entries and handle them. Then, if the count of services we handled is different than the totalCount of entries that match the query, we increase the page number and keep looping. Otherwise, we exit the loop.

Verify if the current day and time match a schedule from the service

Next step, we iterate over every service and take the current day and filter every schedule where openDay <= currentDay <= closeDay. Schedule openDay and closeDay are represent by a number from 0 (Monday) to 6 (Sunday). This is specified by the services rights schema validator.

For every schedule that match this filter, we verify that, using the current time, there are at least one active user inside the service if the current time match the schedule boundaries. We have two types of errors that could occur:

  • There are active users in the service at a time where it doesn’t match the schedule. We create an alarm to warn that these users shouldn’t be active.

  • There are no active users in the service at a time where it match the schedule. We create an alarm to warn that an active service is empty.

Check if we should request more services from the collection

At the end, we verify that the total amount of services we handled match the totalCount of entries retrieved by the query. If not, we increase the page number and continue the loop to fetch the next services and test them.

Script complete

root/scripts/services/check_validity_services.js

main();

function main() {

    let mustFetchServicesFromCollection = true;
    const PAGING_SIZE = 100;
    let PAGING_NUMBER = 0;
    let numberOfServicesTreated = 0;

    const secondsUntilNow = secondsEllapsedFromToday();
    const currentDayAsNumber = getCurrentDayAsNumber();

    while (mustFetchServicesFromCollection) {
        const listResult = collections.listCustomFilter("root.collections.services", PAGING_SIZE, PAGING_NUMBER, `{mandatory: true}`);

        if (isListRequestIsSuccessful(listResult)) {

            const collections = listResult.content.collections;
            const totalCount = listResult.content.totalCount;

            collections.forEach(service => {
                verifyIfServiceHasError(service, currentDayAsNumber, secondsUntilNow);
            });

            numberOfServicesTreated += collections.length;
            PAGING_NUMBER += 1;

            if (numberOfServicesTreated === totalCount) {
                mustFetchServicesFromCollection = false;
            }
        } else {
            mustFetchServicesFromCollection = false;
        }
    }    
}

function isListRequestIsSuccessful(listResult) {
    return listResult.success 
        && listResult.content 
        && listResult.content.hasOwnProperty("collections") 
        && listResult.content.hasOwnProperty("totalCount")
}

function secondsEllapsedFromToday() {
    var localDateTime = new Date(timestamp.format("YYYY-MM-dd HH:mm:ss", Date.now(), "CET"));

    const startOfDay = new Date(localDateTime.getFullYear(), localDateTime.getMonth(), localDateTime.getDate());
    const differenceMilliseconds = localDateTime - startOfDay;
    const secondsElapsed = Math.floor(differenceMilliseconds / 1000);
    return secondsElapsed;
}

function getCurrentDayAsNumber() {
    const now = new Date(timestamp.format("YYYY-MM-dd HH:mm:ss", Date.now(), "CET"));
    const dayOfWeek = now.getDay();
    // Adjust the numbering so that Monday is 0 and Sunday is 6
    const adjustedDayOfWeek = (dayOfWeek - 1 + 7) % 7;
    return adjustedDayOfWeek;
}

function verifyIfServiceHasError(service, currentDayAsNumber, secondsUntilNow) {
    const matchingSchedules = getMatchingScheduleForDayAsNumber(service.schedules, currentDayAsNumber);

    if (matchingSchedules.length == 0 && service.activeUsers.length != 0) {
        //Raise error, no one should be active in this service
        raiseErrorHasActiveUsers(service.name);
    }

    matchingSchedules.forEach(schedule => {
        const openTime = timeConverter(schedule.openTime);
        const closeTime = timeConverter(schedule.closeTime);

        //Schedule is only one day
        if (schedule.openDay === schedule.closeDay) {
            if ((openTime < secondsUntilNow && secondsUntilNow < closeTime) && service.activeUsers.length == 0) {
                //Raise error, someone should have pick this service
                raiseErrorServiceIsEmpty(service.name);
            }
            if ((openTime > secondsUntilNow || closeTime < secondsUntilNow) && service.activeUsers.length != 0) {
                //Raise error, no one should be active in this service
                raiseErrorHasActiveUsers(service.name);
            }
        } else {
            //Schedule is one multiple days. Current day can match either openDay or closeDay.
            //If it doesn't match any, it means it's in between openDay and closeDay.
            //Can't be before openDay or after closeDay because of the filter we do to get the matching schedules

            if (schedule.openDay === currentDayAsNumber) {
                if (openTime < secondsUntilNow && service.activeUsers.length == 0) {
                    //Raise error, someone should have pick this service
                    raiseErrorServiceIsEmpty(service.name);
                }
                if (openTime > secondsUntilNow && service.activeUsers.length != 0) {
                    //Raise error, no one should be active in this service
                    raiseErrorHasActiveUsers(service.name);
                }
            } else if (schedule.closeDay === currentDayAsNumber) {
                if (closeTime > secondsUntilNow && service.activeUsers.length == 0) {
                    //Raise error, someone should have pick this service
                    raiseErrorServiceIsEmpty(service.name);
                }
                if (closeTime < secondsUntilNow && service.activeUsers.length != 0) {
                    //Raise error, no one should be active in this service
                    raiseErrorHasActiveUsers(service.name);
                }
            } else {
                //current day is in between open and close day
                if (service.activeUsers.length == 0) {
                    //Raise error, someone should have pick this service
                    raiseErrorServiceIsEmpty(service.name);
                }
            }
        }
    })
}

function getMatchingScheduleForDayAsNumber(schedules, dayAsNumber) {
    return schedules.filter(schedule => schedule.openDay <= dayAsNumber && dayAsNumber <= schedule.closeDay)
}

function raiseErrorHasActiveUsers(serviceName) {
    return alarms.create(500, `Service [${serviceName}] has active members`, '', 'root.scripts.services', `root.scripts.services-${serviceName}-not-empty`, []);
}

function raiseErrorServiceIsEmpty(serviceName) {
    return alarms.create(500, `Service [${serviceName}] is empty`, '', 'root.scripts.services', `root.scripts.services-${serviceName}-empty`, []);
}

// remove ":" and transforme from "0000" in seconds
function timeConverter(time) {
    const convTime = time.replace(':', '');
    const epoch = ((convTime[0] + convTime[1]) * 3600) + ((convTime[2] + convTime[3]) * 60);
    return epoch
}