How to set a Tempo Report Security done on a Data Store?

Hello to everyone,

I have a question regarding how to set a Tempo Report Security done on a Data Store and not on an active process:

If I create a report on an active process i can drive the security as i wish using the node "Modify Process Security", so for example i can make a specific istance visible by one ore more users, but not by others.

There is a similar way to do so on a Report made starting from a Data store? and what about a Record done departing from the same Data store? In Other words there are some sail functions that help me to set a more specific security?

Thanks

OriginalPostID-203859

OriginalPostID-203859

  Discussion posts and replies are publicly visible

  • Hi Marco check the People Function Plugin in the shared components
  • Hi Marco,

    You can use functions like loggedInUser() to find out the current logged in user and then create the datasubset (from datasource) specific to that particular user (using it as a filter parameter). Now you can base your report on this datasubset. In this way, you can make sure that each user have access to different sets of data. But for this you must have to make some tweaks in your underlying database structure.
  • Does the report need to be different for each Appian group? If so, it would be better to create a different report for each group and use the report security features. If the report needs to be different for each individual user, then the logic and design of the report becomes much more complex. For an individual user report, you could also use the choose() function on loggedInUser() if the number of data sets is limited, or a query where condition like the previous poster mentioned if the data sets is not limited.
  • Ok a more specific requirements are the following:

    •          Every requestor has a manager and a country associated, defined in a database table in the following way (requestor username – manager username) and (requestor name – 1+ country)
    •          Every country has one ore more supervisor defined in the database in the following way (country – 1+ Supervisor)
    •          Once a request is inserted on the database will be saved requestor username and requestor country
    I want that every single request will be seen by:
    •          The requestor
    •          The requestor’s Manager
    •          The supervisor of the requestor’s country

    Thanks
  • Hi

    Create a query rule that fetch Requester, Manager,Supervisor by request ID - you can archive by creating view

    create an expression rule ,let say visibilityOFRequest with input named "users" of type "any type"

    in the record visibility use following rule
    if(
    /* logged in user is a Requester*/
    contains(
               touser(ri!users.requester),
               loggedinuser()
              ),true(),
              if(
               /* logged in user is a Manager*/
               contains(
                         touser(ri!users.Manager),
                         loggedinuser()
                        ),true(),
                        if(
                         /* logged in user is a Supervisor*/
                                  contains(
                                   touser(ri!users.Supervisor),
                                   loggedinuser()
                                  ),true(),false()
               )
              )
    )
  • Hi Marco,

    You can create a view for all requests along with their requester of the request, requester's manager and requester's supervisor (based on the country of requester). And you can create your report upon this view. While fetching the records from the view, add a condition (whether in query entity or query rule) to fetch only those records where logged in user falls is in either of the three roles for a request (requester , requester's manager or requester's supervisor). Below is a code snippet for using filter in queryentity.

    logicalExpression: a!queryLogicalExpression(
    operator: "OR",
    filters: {
    a!queryFilter(
    field: "requestor",
    operator: "=",
    value: loggedInUser()
    ),
    a!queryFilter(
    field: "manager",
    operator: "=",
    value: loggedInUser()
    ),
                        a!queryFilter(
    field: "supervisor",
    operator: "=",
    value: loggedInUser()
    )
    }
    )
  • To the best of my knowledge and as per my analysis, the approaches put forward in the last two comments may not work as intended. As per the problem statement, I could see that one to one isn't possible with supervisor role. Because a supervisor is indirectly derived from a country and in-fact, a country can have more than one supervisor. And country(or countries) is dependent on requestor and I believe there can be more than one country depending on the requestor (requestor name – 1+ country, as said by author of the post). In this case I don't think it is possible to create a view where a request can also hold the country(ies) (one-many between requestor and country), and supervisor(s) (one-many between country and supervisor). We can include the details in the view by using functions namely GROUP_CONCAT etc, but such a view won't be helpful while applying filters against each record.

    @marcoc Hi, I would like to put forward two ways here, where once can be used in filtering the Records in the Record Type and other in obtaining the data from Requests Entity where you may use in a Report.

    >> Record Type:

    You need to derive the identifiers based on the user's role and these identifiers should be applied as filter on the Record Type. Create a rule that derives record identifiers based on the username as follows:

    Name: getRequestIdsFromUserName

    Inputs:
    1. userName

    if(
              ri!userName is requestor,
              {<get the array of request_id from request entity where ri!userName is requestor>}
              if(
                        ri!userName is manager,
                        {<get the array of request_id from request entity where ri!userName is manager>},
                        if(
                                  ri!userName is supervisor,
                                  with(
                                            local!countryIds: <get the array of country_id of which ri!userName is supervisor>,
                                            {<get the array of request_id (identifier of Requests) whose corresponding country_id(or array of country_id) is found in local!countryIds>}
                                  )
                                  {}
                        )
              )
    )

    Finally the above rule should be used in Default Filters as follows: "request_id" "in" rule!getRequestIdsFromUserName(userName:fn!loggedInUser())

    A problem with this approach is that the queries can be costlier in case of supervisor and it hampers performance in case when huge datasets are to be returned. An alternative is that you can turn the Entity Backed Records into Service Backed where you will observe performance and flexibility (downside is that, configuration of Service Backed Records consumes more time) and you can see some threads in the forum that talk about these cases.

    >> Query Requests datastore entity and get data that needs to displayed in an area other than Record

    filter: if(
                                  loggedInUser is requestor,
                                  a!queryFilter(
                                            field: <field_name of requestor>,
                                            operator: "=",
                                            value: loggedInUser()
                                  ),
                                  if(
                                            loggedInUser is manager,
                                            a!queryFilter(
                                             field: <field_name of manager>,
                                             operator: "=",
                                             value: loggedInUser()
                                            ),
                                            if(
                                                      loggedInUser is supervisor,
                                                      with(
                                                                local!countryIds: <get the array of country_id of which ri!userName is supervisor>,
                                                                local!requestIds:{<get the array of request_id whose corresponding country_id(or array of country_id) are found in local!countryIds>},
                                                                if(
                                                                          local!requestIds is null,
                                                                          null,
                                                                          a!queryFilter(
                                                                           field: <field_name of identifier(primary key) of request>,
                                                                           operator: "in",
                                                                           value: local!requestIds
                                                                          )
                                                                )
                                                      ),
                                                      null
                                            )
                                  )
                        )

    The above filter has to be applied while querying Requests datastore entity and we need to bear in mind that batchSize should be effectively used when query is made with supervisor as context.

    Let's see if we could see any better approaches in subsequent comments.
  • Ok thanks for the help, I am trying to implement your solution I only have a lot of problem on getting the all the ID of request that a manager has to see (his request plus the request of every users of which he his manager). But in general how the security of the report is drived is not good for our requirements as before for portal report (before a report wil be seen only if the users can see the process and this can be asily changed using the node modify process secuirity), there is a possibility that in future version, hopefully in 16.1, a similar way to modify the report security will be implemented?
  • @marcoc True, also the queries can become costlier with the increasing volumes of data.

    Appian has come up with an alternate approach of implementing the security by making use of Record Viewer Groups as specified at https://forum.appian.com/suite/help/16.1/Record_Level_Security_for_Entity_Backed_Records_Best_Practice.html but even this isn't a clean way of implementing the security. Further Appian themselves has stated that this approach doesn't hold good in some cases in the 'Performance Tips' section in the same page. Though they specified one more alternative (again it's mentioned in the 'Performance Tips' section), we can not apply it all the times.

    To the best of my knowledge, as of now the only clean way of doing it is by turning your Entity Backed Records into Service Backed Records. In Service Backed Records, an expression acts as a source, and off-course, you need to query the datastore entities in the expression. But the only advantage is that you can minimise the number of records you query whereas in Entity Backed Records we wouldn't have such control and end up in obtaining entire dataset. Performance and Flexibility are added advantages. But downside of this approach is that the configuration consumes good amount of time. If interested in this approach, you may take a look at https://forum.appian.com/suite/help/7.8/Records_Tutorial.html#Create_Service-Backed_Records which will demonstrate the way to do.

    In case, if your intention is to just query the datastore entity and surface the data in a component such as paging grid which will be included in the report or dashboard thereafter, make sure that queries are made in such a way that batchSize is effectively used. As long as the batchSize is used in an efficient way, very minimal datsets will be obtained which won't effect performance.