QPR ProcessAnalyzer API: Difference between revisions

From QPR ProcessAnalyzer Wiki
Jump to navigation Jump to search
 
(151 intermediate revisions by 2 users not shown)
Line 1: Line 1:
QPR ProcessAnalyzer Web Service API (Application Programming Interface) can be used to automate operations and to create integration with other applications. All Web Service operations only accept HTTP POST method (HTTP GET is not allowed).
== Introduction ==
QPR ProcessAnalyzer API can be used to create integrations to other applications and automate operations in the process mining environment.


== Functions in the Web Service API ==
QPR ProcessAnalyzer API is a JSON-based API following the REST design principles. Most of the endpoints require a prior login to establish a session. The session is initialized with the [[Web_API:_Token|token]] call with username and password, and the access token is returned as a response for a successful login. The endpoints requiring prior authenticated session, need to have a HTTP request header ''Authorization'' with value ''Bearer <access token>'' to identify the session.
The following functions are available:
* [[QPR ProcessAnalyzer API: Authenticate|Authenticate]]: Tries to authenticate given user with given password and authentication parameters.
* [[QPR ProcessAnalyzer API: GetAnalysisImageAsByteArray|GetAnalysisImageAsByteArray]]: Get [[Flowchart Analysis in QPR ProcessAnalyzer Excel Client|Flowchart Analysis]] as image.
* [[QPR ProcessAnalyzer API: GetModels|GetModels]]: Can be used to query QPR ProcessAnalyzer model related information.
* [[QPR ProcessAnalyzer API: GetStream|GetStream]] can be used to query contents of a stream bound to the given session identified by given stream id.
* [[QPR ProcessAnalyzer API: QueryObjectProperties|QueryObjectProperties]] returns all the listed properties queried for all the listed objects identified by unique identifiers.
* [[QPR ProcessAnalyzer API: ResetModelCache|ResetModelCache]]: Can be used to clear all cached model information of the given model.
* [[QPR ProcessAnalyzer API: RunScript|RunScript]] can be used to execute given PA script using given parameters.
* [[QPR ProcessAnalyzer API: SetModel|SetModel]]: Can be used to set model related information.
* [[QPR ProcessAnalyzer API: ValidateModel|ValidateModel]] can be used to perform all the pending tasks stored in the work queue of the given model.


== Common Properties for all Object Types ==
Url for calling the API has the following form (replace the server hostname with a correct one):
The following properties are supported by all ProcessAnalyzer object types. These properties are used in the '''properties''' parameter of the [[QPR ProcessAnalyzer API: QueryObjectProperties|QueryObjectProperties]] function:
<pre>
* '''typename''': Name of the type of the object.
https://customer.onqpr.com/qprpa/api/<endpointName>
* '''name''': Name of the given object.
</pre>
* '''properties''': List of all the supported properties for given object.
 
* '''relatedcount''': Integer number of how many child nodes there are in the next level of given hierarchy. This is 0 if the element doesn't support the relation or there are no child objects for the given object in given hierarchy. Requires hierarchy-parameter to be defined.
== Available endpoints ==
Following endpoints are available:
{| class="wikitable"
!'''Endpoint'''
! '''Description'''
|-
||[[Web_API:_Token|token]]
||Login user using username and password and get a session token as a response.
|-
||[[Web_API:_Signout|api/signout]]
||Logs out a user session.
|-
||[[Web_API:_Expression|api/expression]]
||Runs an expression.
|-
||[[Web_API:_Expression/query|api/expression/query]]
||Runs query written using the expression language and returns result data as response.
|-
||[[Web_API:_Filters|api/filters]]
||Get filters for all models or filters for a single model.
|-
||[[Web_API:_Serverinfo|api/serverinfo]]
||Returns common system information needed by UI, such as the default UI language and in whether SSO has been configured.
|-
||[[Web_API:_Importfile|api/importfile]]
||Import data into datatable from .csv, .xes or .pacm file.
|-
||[[Web_API:_Usersettings|api/usersettings]]
||Save user specific settings to the server.
|-
||[[Web_API:_Operations/terminate|api/operations/terminate]]
||Stops the defined tasks (by the task id) to save computing resources.
|-
||[[Web_API:_Cancel|api/analysis/cancel]]
||Stops currently running tasks (by the task identifier) to save computing resources.
|-
||[[Web_API:_saml2/acs|Saml2/Acs]]
||Identity provider (IdP) will send the SAML 2.0 assertion to this endpoint which will respond with 302 to redirect to QPR ProcessAnalyzer UI.
|-
||[[Web_API:_saml2|Saml2]]
||Returns the SAML 2.0 service provider (SP) metadata, if SAML 2.0 authentication has been configured.
|}
 
In addition, there are endpoints for
* [[Web API for Workspace Elements|moving and deleting workspace elements]]
* [[Web_API_for_Projects|projects]]
* [[Web_API_for_Dashboards|dashboards]]
* [[Web_API_for_Models|models]]
* [[Web_API_for_Datatables|datatables]]
* [[Web_API_for_Scripts|scripts]]
* [[Web_API_for_User_Management|users, groups and roles]]
 
== Usage examples ==
=== Run query to fetch data ===
Following Python function performs a query to fetch data from QPR ProcessAnalyzer by calling the REST API. It performs following steps: (1) login to QPR ProcessAnalyzer, (2) run the query, (3) write fetched data to a file, and (4) log out.
 
The query is in the "json" parameter of the /api/expression/query request. For example, query for a chart can be found in the chart settings ''Advanced'' tab by clicking the ''Query'' button.
 
<syntaxhighlight lang="python" line>
def runQuery(serverUrl: str, username: str, password: str):
  import requests
  import json
  loginData = {
    "grant_type": "password",
    "username": username,
    "password": password
  }
  loginResponse = requests.post(
    url = serverUrl + "/token",
    data = loginData
  )
  loginResponse.raise_for_status()
  sessionToken = loginResponse.json().get("access_token")
 
  queryResponse = requests.post(
    url = serverUrl + "/api/expression/query",
    headers = {
      "Authorization": "Bearer " + sessionToken
    },
    json = {
      "ProcessingMethod": "dataframe",
      "ContextType": "model",
      "ModelId": 1,
      "Root": "Cases",
      "MaximumRowCount": 10,
      "Dimensions": None,
      "Values": [
        {
          "Name": "CaseId",
          "Expression": "CaseId"
        },
        {
          "Name": "StartTime",
          "Expression": "AggregateFrom(Events, \"Min\", TimeStamp)"
        },
        {
          "Name": "EndTime",
          "Expression": "AggregateFrom(Events, \"Max\", TimeStamp)"
        },
        {
          "Name": "EventCount",
          "Expression": "AggregateFrom(Events, \"Count\")"
        },
        {
          "Name": "EventTypeCount",
          "Expression": "AggregateFrom(Events, \"CountDistinct\", EventType)"
        }
      ],
      "Ordering": [
        {
          "Name": "CaseId",
          "Direction": "Ascending"
        }
      ]
    }
  )
  queryResponse.raise_for_status()
   
  with open("QueriedData.json", "w") as f:
    json.dump(queryResponse.json(), f)
 
  logOutResponse = requests.post(
    url = serverUrl + "/api/signout",
    headers = {
      "Authorization": "Bearer " + sessionToken,
      "Content-type": "application/json"
    }
  )
  logOutResponse.raise_for_status()
</syntaxhighlight>
 
The function can be called as follows:
<syntaxhighlight lang="python" line>
runQuery(
  serverUrl = "https://server.onqpr.com/qprpa",
  username = "qpr",
  password = "demo"
)
</syntaxhighlight>
 
=== Trigger script run ===
Following Python function starts a script in QPR ProcessAnalyzer by calling the REST API. It performs following steps: (1) login to QPR ProcessAnalyzer, (2) start the script, and (3) log out. The call just starts the script without waiting for it to complete (asynchronous behavior).


== Object Types ==
<syntaxhighlight lang="python" line>
=== DataTable ===
def startQprProcessAnalyzerScript(serverUrl: str, username: str, password: str, scriptId: int):
The following properties are supported by the DataTable object type:
  import requests
* '''typename''': "DataTable"
  loginData = {
* '''<column identifier>''': A Data Table column name converted to script name and also prefixed with"custom_" when used as an object property name in QueryObjectProperties. For example: when the Data Table column name is "Actual", the column identifier is "custom_actual".
    "grant_type": "password",
* All [[QPR ProcessAnalyzer Web Service API#Common Properties for all Object Types|common properties]]
    "username": username,
    "password": password
  }
  loginResponse = requests.post(
    url = serverUrl + "/token",
    data = loginData
  )
  loginResponse.raise_for_status()
  sessionToken = loginResponse.json().get("access_token")
 
  startScriptResponse = requests.post(
    url = serverUrl + "/api/scripts/run/" + str(scriptId),
    headers = {
      "Authorization": "Bearer " + sessionToken,
      "Content-type": "application/json"
    }
  )
  startScriptResponse.raise_for_status()


=== Product ===
  logOutResponse = requests.post(
Properties of Product object type:
    url = serverUrl + "/api/signout",
* '''typename''': "product"
    headers = {
* '''name''': Name of the product (QPR ProcessAnalyzer)
      "Authorization": "Bearer " + sessionToken,
* '''version''': Dll version of the Qpr.ProcessAnalyzer.Core.dll
      "Content-type": "application/json"
* All [[QPR ProcessAnalyzer Web Service API#Common Properties for all Object Types|common properties]]
    }
  )
  logOutResponse.raise_for_status()
</syntaxhighlight>


Relations of Product object type:
The function can be called as follows:
* '''related''': Returns the related objects. Supported relation hierarchies is '''datatable''' which returns all the projects available for the user.
<syntaxhighlight lang="python" line>
startQprProcessAnalyzerScript(
  serverUrl = "https://server.onqpr.com/qprpa",
  username = "qpr",
  password = "demo",
  scriptId = 1
)
</syntaxhighlight>
The script id can be found in the scripts list in the Workspace.


=== Project ===
=== Synchronize users and groups ===
Properties of the Project object type:
Following script written in Python updates QPR ProcessAnalyzer users and groups based on the provided dataset. This script can be extended to fetch the user data from an external source (e.g., Azure AD) to implement a complete user management integration between the systems.
* '''typename''': "Project"
* All [[QPR ProcessAnalyzer Web Service API#Common Properties for all Object Types|common properties]]


Relations of the Project object type:
This script performs following steps:
* '''related''': Returns the related objects. Supported relation hierarchies: '''datatable''' which returns all the data tables in given project available for the user.
# Read the provided dataset and store it to in-memory structures.
# Read all users from QPR ProcessAnalyzer (including which groups the users belong to). (POST /api/expression/query)
# Read all groups from QPR ProcessAnalyzer. (POST /api/expression/query)
# Determine the gap between the current state in the user management and the provided dataset.
# Create new users appearing in the dataset to QPR ProcessAnalyzer. (POST /api/users)
# Inactivate non-existing users in the dataset from QPR ProcessAnalyzer. (PUT /api/users)
# Activate existing inactive users in QPR ProcessAnalyzer that exist in the dataset. (PUT /api/users)
# Add users to groups and remove from groups based on the determined gap in the state. (PUT/DELETE /api/users/memberships)


== Identifying QPR ProcessAnalyzer Objects ==
Python script code:
QPR ProcessAnalyzer unique identifiers are used to uniquely identify any object in QPR ProcessAnalyzer. The format of a unique identifier is:
<syntaxhighlight lang="python" line>
def synchronizeUsersAndGroups(serverUrl: str, username: str, password: str, userData: list, userGroupData: list):
    """
    Synchronizes QPR ProcessAnalyzer users and groups based on the provided dataset.
   
    Parameters:
    - serverUrl: QPR ProcessAnalyzer server URL
    - username: Admin username for authentication
    - password: Admin password for authentication
    - userData: List of dicts with keys "Name" and "Groups" (list of group names)
    - userGroupData: List of group names that should exist
    """
    import requests
    import json


'''PA.<type>.<object>'''
    # Step 1: Login to QPR ProcessAnalyzer
    loginData = {
        "grant_type": "password",
        "username": username,
        "password": password
    }
    loginResponse = requests.post(
        url=serverUrl + "/token",
        data=loginData
    )
    loginResponse.raise_for_status()
    sessionToken = loginResponse.json().get("access_token")


In the format, '''<type>''' can be any of the following:
    headers = {
* '''0''': undefined (reserved, do not use)
        "Authorization": "Bearer " + sessionToken,
* '''1''': project
        "Content-type": "application/json"
* '''2''': data table
    }
* '''3''': model
* '''4''': filter
* '''5''': bookmark


== Example Usage==
    # Step 2: Read all users from QPR ProcessAnalyzer (including group memberships)
<pre>
    usersQueryResponse = requests.post(
//login                  
        url=serverUrl + "/api/expression/query",
$.ajax({
        headers=headers,
  "method": "POST",
        json={
  "url": "http://localhost/qprpa/Mainservice.svc/webHttp/Authenticate",
            "Dimensions": None,
  "dataType": "json", "contentType": "application/json; charset=utf-8",
            "Values": [
  "data": JSON.stringify({
                {
     'logOnName': '<username>',
                    "Name": "Id",
     'password': '<password>',
                    "Expression": "Id"
    'parameters': ''
                },
  })
                {
});                     
                    "Name": "Name",
                    "Expression": "Name"
                 },
                {
                    "Name": "IsActive",
                    "Expression": "IsActive"
                },
                {
                    "Name": "Groups",
                    "Expression": "ToJson(OrderByValue(Groups.Id))"
                }
            ],
            "Root": "Users",
            "ContextType": "generic"
        }
    )
    usersQueryResponse.raise_for_status()
    existingUsers = usersQueryResponse.json()
 
    # Step 3: Read all groups from QPR ProcessAnalyzer
    groupsQueryResponse = requests.post(
        url=serverUrl + "/api/expression/query",
        headers=headers,
        json={
            "Dimensions": None,
            "Values": [
                {
                    "Name": "Id",
                    "Expression": "Id"
                },
                {
                    "Name": "Name",
                    "Expression": "Name"
                }
            ],
            "Root": "UserGroups",
            "ContextType": "generic"
        }
    )
    groupsQueryResponse.raise_for_status()
    existingGroups = groupsQueryResponse.json()
 
    # Build lookup dictionaries
    existingUsersByName = {}
     for user in existingUsers:
        existingUsersByName[user["Name"]] = user
 
    existingGroupsByName = {}
    for group in existingGroups:
        existingGroupsByName[group["Name"]] = group
 
    # Build set of desired usernames from the provided dataset
    desiredUserNames = set()
    for user in userData:
        desiredUserNames.add(user["Name"])
 
    # Build desired group memberships: username -> set of group names
    desiredMemberships = {}
    for user in userData:
        desiredMemberships[user["Name"]] = set(user.get("Groups", []))
 
     # Step 4: Determine the gap between current state and desired state
 
    existingUserNames = set(existingUsersByName.keys())
    usersToCreate = desiredUserNames - existingUserNames
    usersToInactivate = existingUserNames - desiredUserNames
    usersToActivate = set()
 
    for userName in desiredUserNames:
        if userName in existingUsersByName:
            user = existingUsersByName[userName]
            if not user["IsActive"]:
                usersToActivate.add(userName)
 
    # Step 5: Create new users appearing in the dataset
    for userName in usersToCreate:
        createUserResponse = requests.post(
            url=serverUrl + "/api/users",
            headers=headers,
            json={
                "Name": userName,
                "IsActive": True
            }
        )
        createUserResponse.raise_for_status()
        createdUser = createUserResponse.json()
        # Add the newly created user to our lookup
        existingUsersByName[userName] = {
            "Id": createdUser["Id"],
            "Name": userName,
            "IsActive": True,
            "Groups": "[]"
        }


//create user
    # Step 6: Inactivate users not in the dataset
$.ajax({
    for userName in usersToInactivate:
  "method": "POST",
        user = existingUsersByName[userName]
  "url": "http://localhost/qprpa/Mainservice.svc/webHttp/SetUser",
        if user["IsActive"]:
  "dataType": "json", "contentType": "application/json; charset=utf-8",
            inactivateResponse = requests.put(
  "data": JSON.stringify({  
                url=serverUrl + "/api/users",
    "sessionId": "547c1aa5-e85b-4642-bbb1-8cb656015002",
                headers=headers,
    "user": {"Name": "user", "FullName": "first last" },
                json={
    "parameters": [{"Key": "Password", "Value": "demo"}]
                    "Id": user["Id"],
  })
                    "IsActive": False
});
                }
            )
            inactivateResponse.raise_for_status()


//add user to group, value 8:12:0 is user:group:member type
    # Step 7: Activate existing inactive users that are in the dataset
$.ajax({
    for userName in usersToActivate:
  "method": "POST",
        user = existingUsersByName[userName]
  "url": "http://localhost/qprpa/Mainservice.svc/webHttp/ModifyUserRelations",
        activateResponse = requests.put(
  "dataType": "json", "contentType": "application/json; charset=utf-8",
            url=serverUrl + "/api/users",
  "data": JSON.stringify({  
            headers=headers,
    "sessionId": "749dcbdb-e57b-434b-a739-1f4ddc7ebc30",
            json={
    "parameters": [{"Key": "AddGroups", "Value": "8:12:0"}]
                "Id": user["Id"],
  })
                "IsActive": True
});
            }
        )
        activateResponse.raise_for_status()


//log off
    # Step 8: Add users to groups and remove from groups based on the gap
$.ajax({
    for userName in desiredUserNames:
  "method": "POST",
        user = existingUsersByName[userName]
  "url": "http://localhost/qprpa/Mainservice.svc/webHttp/LogOff",
        userId = user["Id"]
  "dataType": "json", "contentType": "application/json; charset=utf-8",
  "data": JSON.stringify({
    "sessionId":"75aa3d08-5ad9-4b0b-8981-7daca98348cd"
  })
});
</pre>


== PowerShell example of listing users ==
        # Parse current group IDs from the user's Groups field
<pre>
        currentGroupIds = set()
$paService=New-WebServiceProxy –Uri "http://localhost/qprpa/MainService.svc"
        groupsJson = user.get("Groups", "[]")
$connection=$paService.Authenticate("username", "password", @())
        if groupsJson:
$token=$connection.GetValue(0).Value
            currentGroupIds = set(json.loads(groupsJson))


$param=@()
        # Determine desired group IDs for this user
$users=$paService.GetUsers($token, $null, $param)
        desiredGroupIds = set()
$users
        for groupName in desiredMemberships.get(userName, []):
$paService | get-member | ? {$_.definition -match "GetAnalysis"}
            if groupName in existingGroupsByName:
</pre>
                desiredGroupIds.add(existingGroupsByName[groupName]["Id"])


== PowerShell example update model configuration ==
        # Groups to add the user to
<pre>
        groupsToAdd = desiredGroupIds - currentGroupIds
$paService=New-WebServiceProxy –Uri "http://localhost/qprpa/MainService.svc"
        # Groups to remove the user from
$connection=$paService.Authenticate("username", "password", @())
        groupsToRemove = currentGroupIds - desiredGroupIds
$token=$connection.GetValue(0).Value
$param=@()
$modelId =@(2)
$model=$paService.GetModels($token,$modelId, $param)


$model[0]
        # Add user to new groups
$model[0].ConfigurationJson = "{}"
        for groupId in groupsToAdd:
            addMembershipResponse = requests.put(
                url=serverUrl + "/api/users/memberships",
                headers=headers,
                json={
                    "GroupId": groupId,
                    "MemberId": userId,
                    "RoleName": "Member"
                }
            )
            addMembershipResponse.raise_for_status()


$paService.SetModel($token,$model[0], $param)
        # Remove user from groups
</pre>
        for groupId in groupsToRemove:
            removeMembershipResponse = requests.delete(
                url=serverUrl + "/api/users/memberships",
                headers=headers,
                json={
                    "GroupId": groupId,
                    "MemberId": userId,
                    "RoleName": "Member"
                }
            )
            removeMembershipResponse.raise_for_status()


== PowerShell example get flowchart image ==
    # Log out
<pre>
    logOutResponse = requests.post(
$paService=New-WebServiceProxy –Uri "http://localhost/qprpa/MainService.svc"
        url=serverUrl + "/api/signout",
$connection=$paService.Authenticate("username", "password", @())
        headers=headers
$token=$connection.GetValue(0).Value
    )
    logOutResponse.raise_for_status()


$filterId = 13
</syntaxhighlight>
$analysisType = 0
$processAnalysisType = 4
$minTransitionPercentage = 0.0


$png= $paService.GetAnalysisImageAsByteArray($token,$filterId,1, $analysisType,1, $processAnalysisType,1 , $minTransitionPercentage,1)
The script can be called as follows:
[IO.File]::WriteAllBytes('c:\tmp\image.png', $png)
<syntaxhighlight lang="python" line>
</pre>
synchronizeUsersAndGroups(
    serverUrl="https://server.onqpr.com/qprpa",
    username="qpr",
    password="demo",
    userData=[
        {"Name": "user1@company.com", "Groups": ["Analysts", "Managers"]},
        {"Name": "user2@company.com", "Groups": ["Analysts"]},
        {"Name": "user3@company.com", "Groups": ["Managers", "Admins"]}
    ],
    userGroupData=["Analysts", "Managers", "Admins"]
)
</syntaxhighlight>

Latest revision as of 10:37, 7 May 2026

Introduction

QPR ProcessAnalyzer API can be used to create integrations to other applications and automate operations in the process mining environment.

QPR ProcessAnalyzer API is a JSON-based API following the REST design principles. Most of the endpoints require a prior login to establish a session. The session is initialized with the token call with username and password, and the access token is returned as a response for a successful login. The endpoints requiring prior authenticated session, need to have a HTTP request header Authorization with value Bearer <access token> to identify the session.

Url for calling the API has the following form (replace the server hostname with a correct one):

https://customer.onqpr.com/qprpa/api/<endpointName>

Available endpoints

Following endpoints are available:

Endpoint Description
token Login user using username and password and get a session token as a response.
api/signout Logs out a user session.
api/expression Runs an expression.
api/expression/query Runs query written using the expression language and returns result data as response.
api/filters Get filters for all models or filters for a single model.
api/serverinfo Returns common system information needed by UI, such as the default UI language and in whether SSO has been configured.
api/importfile Import data into datatable from .csv, .xes or .pacm file.
api/usersettings Save user specific settings to the server.
api/operations/terminate Stops the defined tasks (by the task id) to save computing resources.
api/analysis/cancel Stops currently running tasks (by the task identifier) to save computing resources.
Saml2/Acs Identity provider (IdP) will send the SAML 2.0 assertion to this endpoint which will respond with 302 to redirect to QPR ProcessAnalyzer UI.
Saml2 Returns the SAML 2.0 service provider (SP) metadata, if SAML 2.0 authentication has been configured.

In addition, there are endpoints for

Usage examples

Run query to fetch data

Following Python function performs a query to fetch data from QPR ProcessAnalyzer by calling the REST API. It performs following steps: (1) login to QPR ProcessAnalyzer, (2) run the query, (3) write fetched data to a file, and (4) log out.

The query is in the "json" parameter of the /api/expression/query request. For example, query for a chart can be found in the chart settings Advanced tab by clicking the Query button.

def runQuery(serverUrl: str, username: str, password: str):
  import requests
  import json
  loginData = {
    "grant_type": "password",
    "username": username,
    "password": password
  }
  loginResponse = requests.post(
    url = serverUrl + "/token",
    data = loginData
  )
  loginResponse.raise_for_status()
  sessionToken = loginResponse.json().get("access_token")
  
  queryResponse = requests.post(
    url = serverUrl + "/api/expression/query",
    headers = {
      "Authorization": "Bearer " + sessionToken
    },
    json = {
      "ProcessingMethod": "dataframe",
      "ContextType": "model",
      "ModelId": 1,
      "Root": "Cases",
      "MaximumRowCount": 10,
      "Dimensions": None,
      "Values": [
        {
          "Name": "CaseId",
          "Expression": "CaseId"
        },
        {
          "Name": "StartTime",
          "Expression": "AggregateFrom(Events, \"Min\", TimeStamp)"
        },
        {
          "Name": "EndTime",
          "Expression": "AggregateFrom(Events, \"Max\", TimeStamp)"
        },
        {
          "Name": "EventCount",
          "Expression": "AggregateFrom(Events, \"Count\")"
        },
        {
          "Name": "EventTypeCount",
          "Expression": "AggregateFrom(Events, \"CountDistinct\", EventType)"
        }
      ],
      "Ordering": [
        {
          "Name": "CaseId",
          "Direction": "Ascending"
        }
      ]
    }
  )
  queryResponse.raise_for_status()
    
  with open("QueriedData.json", "w") as f:
    json.dump(queryResponse.json(), f)

  logOutResponse = requests.post(
    url = serverUrl + "/api/signout",
    headers = {
      "Authorization": "Bearer " + sessionToken,
      "Content-type": "application/json"
    }
  )
  logOutResponse.raise_for_status()

The function can be called as follows:

runQuery(
  serverUrl = "https://server.onqpr.com/qprpa",
  username = "qpr",
  password = "demo"
)

Trigger script run

Following Python function starts a script in QPR ProcessAnalyzer by calling the REST API. It performs following steps: (1) login to QPR ProcessAnalyzer, (2) start the script, and (3) log out. The call just starts the script without waiting for it to complete (asynchronous behavior).

def startQprProcessAnalyzerScript(serverUrl: str, username: str, password: str, scriptId: int):
  import requests
  loginData = {
    "grant_type": "password",
    "username": username,
    "password": password
  }
  loginResponse = requests.post(
    url = serverUrl + "/token",
    data = loginData
  )
  loginResponse.raise_for_status()
  sessionToken = loginResponse.json().get("access_token")
  
  startScriptResponse = requests.post(
    url = serverUrl + "/api/scripts/run/" + str(scriptId),
    headers = {
      "Authorization": "Bearer " + sessionToken,
      "Content-type": "application/json"
    }
  )
  startScriptResponse.raise_for_status()

  logOutResponse = requests.post(
    url = serverUrl + "/api/signout",
    headers = {
      "Authorization": "Bearer " + sessionToken,
      "Content-type": "application/json"
    }
  )
  logOutResponse.raise_for_status()

The function can be called as follows:

startQprProcessAnalyzerScript(
  serverUrl = "https://server.onqpr.com/qprpa",
  username = "qpr",
  password = "demo",
  scriptId = 1
)

The script id can be found in the scripts list in the Workspace.

Synchronize users and groups

Following script written in Python updates QPR ProcessAnalyzer users and groups based on the provided dataset. This script can be extended to fetch the user data from an external source (e.g., Azure AD) to implement a complete user management integration between the systems.

This script performs following steps:

  1. Read the provided dataset and store it to in-memory structures.
  2. Read all users from QPR ProcessAnalyzer (including which groups the users belong to). (POST /api/expression/query)
  3. Read all groups from QPR ProcessAnalyzer. (POST /api/expression/query)
  4. Determine the gap between the current state in the user management and the provided dataset.
  5. Create new users appearing in the dataset to QPR ProcessAnalyzer. (POST /api/users)
  6. Inactivate non-existing users in the dataset from QPR ProcessAnalyzer. (PUT /api/users)
  7. Activate existing inactive users in QPR ProcessAnalyzer that exist in the dataset. (PUT /api/users)
  8. Add users to groups and remove from groups based on the determined gap in the state. (PUT/DELETE /api/users/memberships)

Python script code:

def synchronizeUsersAndGroups(serverUrl: str, username: str, password: str, userData: list, userGroupData: list):
    """
    Synchronizes QPR ProcessAnalyzer users and groups based on the provided dataset.
    
    Parameters:
    - serverUrl: QPR ProcessAnalyzer server URL
    - username: Admin username for authentication
    - password: Admin password for authentication
    - userData: List of dicts with keys "Name" and "Groups" (list of group names)
    - userGroupData: List of group names that should exist
    """
    import requests
    import json

    # Step 1: Login to QPR ProcessAnalyzer
    loginData = {
        "grant_type": "password",
        "username": username,
        "password": password
    }
    loginResponse = requests.post(
        url=serverUrl + "/token",
        data=loginData
    )
    loginResponse.raise_for_status()
    sessionToken = loginResponse.json().get("access_token")

    headers = {
        "Authorization": "Bearer " + sessionToken,
        "Content-type": "application/json"
    }

    # Step 2: Read all users from QPR ProcessAnalyzer (including group memberships)
    usersQueryResponse = requests.post(
        url=serverUrl + "/api/expression/query",
        headers=headers,
        json={
            "Dimensions": None,
            "Values": [
                {
                    "Name": "Id",
                    "Expression": "Id"
                },
                {
                    "Name": "Name",
                    "Expression": "Name"
                },
                {
                    "Name": "IsActive",
                    "Expression": "IsActive"
                },
                {
                    "Name": "Groups",
                    "Expression": "ToJson(OrderByValue(Groups.Id))"
                }
            ],
            "Root": "Users",
            "ContextType": "generic"
        }
    )
    usersQueryResponse.raise_for_status()
    existingUsers = usersQueryResponse.json()

    # Step 3: Read all groups from QPR ProcessAnalyzer
    groupsQueryResponse = requests.post(
        url=serverUrl + "/api/expression/query",
        headers=headers,
        json={
            "Dimensions": None,
            "Values": [
                {
                    "Name": "Id",
                    "Expression": "Id"
                },
                {
                    "Name": "Name",
                    "Expression": "Name"
                }
            ],
            "Root": "UserGroups",
            "ContextType": "generic"
        }
    )
    groupsQueryResponse.raise_for_status()
    existingGroups = groupsQueryResponse.json()

    # Build lookup dictionaries
    existingUsersByName = {}
    for user in existingUsers:
        existingUsersByName[user["Name"]] = user

    existingGroupsByName = {}
    for group in existingGroups:
        existingGroupsByName[group["Name"]] = group

    # Build set of desired usernames from the provided dataset
    desiredUserNames = set()
    for user in userData:
        desiredUserNames.add(user["Name"])

    # Build desired group memberships: username -> set of group names
    desiredMemberships = {}
    for user in userData:
        desiredMemberships[user["Name"]] = set(user.get("Groups", []))

    # Step 4: Determine the gap between current state and desired state

    existingUserNames = set(existingUsersByName.keys())
    usersToCreate = desiredUserNames - existingUserNames
    usersToInactivate = existingUserNames - desiredUserNames
    usersToActivate = set()

    for userName in desiredUserNames:
        if userName in existingUsersByName:
            user = existingUsersByName[userName]
            if not user["IsActive"]:
                usersToActivate.add(userName)

    # Step 5: Create new users appearing in the dataset
    for userName in usersToCreate:
        createUserResponse = requests.post(
            url=serverUrl + "/api/users",
            headers=headers,
            json={
                "Name": userName,
                "IsActive": True
            }
        )
        createUserResponse.raise_for_status()
        createdUser = createUserResponse.json()
        # Add the newly created user to our lookup
        existingUsersByName[userName] = {
            "Id": createdUser["Id"],
            "Name": userName,
            "IsActive": True,
            "Groups": "[]"
        }

    # Step 6: Inactivate users not in the dataset
    for userName in usersToInactivate:
        user = existingUsersByName[userName]
        if user["IsActive"]:
            inactivateResponse = requests.put(
                url=serverUrl + "/api/users",
                headers=headers,
                json={
                    "Id": user["Id"],
                    "IsActive": False
                }
            )
            inactivateResponse.raise_for_status()

    # Step 7: Activate existing inactive users that are in the dataset
    for userName in usersToActivate:
        user = existingUsersByName[userName]
        activateResponse = requests.put(
            url=serverUrl + "/api/users",
            headers=headers,
            json={
                "Id": user["Id"],
                "IsActive": True
            }
        )
        activateResponse.raise_for_status()

    # Step 8: Add users to groups and remove from groups based on the gap
    for userName in desiredUserNames:
        user = existingUsersByName[userName]
        userId = user["Id"]

        # Parse current group IDs from the user's Groups field
        currentGroupIds = set()
        groupsJson = user.get("Groups", "[]")
        if groupsJson:
            currentGroupIds = set(json.loads(groupsJson))

        # Determine desired group IDs for this user
        desiredGroupIds = set()
        for groupName in desiredMemberships.get(userName, []):
            if groupName in existingGroupsByName:
                desiredGroupIds.add(existingGroupsByName[groupName]["Id"])

        # Groups to add the user to
        groupsToAdd = desiredGroupIds - currentGroupIds
        # Groups to remove the user from
        groupsToRemove = currentGroupIds - desiredGroupIds

        # Add user to new groups
        for groupId in groupsToAdd:
            addMembershipResponse = requests.put(
                url=serverUrl + "/api/users/memberships",
                headers=headers,
                json={
                    "GroupId": groupId,
                    "MemberId": userId,
                    "RoleName": "Member"
                }
            )
            addMembershipResponse.raise_for_status()

        # Remove user from groups
        for groupId in groupsToRemove:
            removeMembershipResponse = requests.delete(
                url=serverUrl + "/api/users/memberships",
                headers=headers,
                json={
                    "GroupId": groupId,
                    "MemberId": userId,
                    "RoleName": "Member"
                }
            )
            removeMembershipResponse.raise_for_status()

    # Log out
    logOutResponse = requests.post(
        url=serverUrl + "/api/signout",
        headers=headers
    )
    logOutResponse.raise_for_status()

The script can be called as follows:

synchronizeUsersAndGroups(
    serverUrl="https://server.onqpr.com/qprpa",
    username="qpr",
    password="demo",
    userData=[
        {"Name": "user1@company.com", "Groups": ["Analysts", "Managers"]},
        {"Name": "user2@company.com", "Groups": ["Analysts"]},
        {"Name": "user3@company.com", "Groups": ["Managers", "Admins"]}
    ],
    userGroupData=["Analysts", "Managers", "Admins"]
)