# Role Based Menu Navigation - Bug

**URL:** <https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [November 27, 2024, 7:15am UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167 "2024-11-27T07:15:31Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Aiasa21](https://avatars.discourse-cdn.com/v4/letter/a/aeb1de/32.png) [@Aiasa21](https://forum.inductiveautomation.com/u/Aiasa21)\
**Post date:** [November 27, 2024, 7:15am UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/1 "2024-11-27T07:15:31Z")

</div>

Hello everybody,  
I'm using this resource: [Role Based Menu Navigation](https://inductiveautomation.com/exchange/2281/overview)

I found a bug in the resource and I don't know how to fix it, so I hope you can help me.

When you add a view you can set the "Required Roles" property.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/7/c72fd35de641b4b1edddbc31692e8f4b34d09eff.png)

So now this is the procedure to make the bug happens:

1. Add roles to the view
2. Submit
3. Remove all the roles to the view
4. Submit
5. At this moment the page return this error:

```python
Traceback (most recent call last): File "<transform>", line 53, in transform File "<transform>", line 34, in buildInstance File "<transform>", line 34, in buildInstance File "<transform>", line 7, in buildInstance TypeError: object of type 'bool' has no len()

```

And the only way to fix this error is to find in the "menu tree" that specific page where the `requiredRoles` has become a bool and convert to an array

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/f/7/f74103665f2f574b6a36b20e8e3d5a5ee4ca4bb7.png)

Thank you for the help

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [November 27, 2024, 12:12pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/2 "2024-11-27T12:12:02Z")

</div>

You should report this to resource's author.

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [November 27, 2024, 1:33pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/3 "2024-11-27T13:33:08Z")

</div>

The resource is quite old, i doubt it's gonna get updated.  
But still worth a try,  
Could also show us the code that addes the role, maybe its an easy fix

---

<div class="post-metadata">

**Author:** ![Aiasa21](https://avatars.discourse-cdn.com/v4/letter/a/aeb1de/32.png) [@Aiasa21](https://forum.inductiveautomation.com/u/Aiasa21)\
**Post date:** [November 27, 2024, 5:24pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/4 "2024-11-27T17:24:22Z")

</div>

> [@pturmel](#):
>
> You should report this to resource's author.

I've already tried to concact the author for another question but without any answer.

> [@victordcq](#):
>
> The resource is quite old, i doubt it's gonna get updated.  
> But still worth a try,  
> Could also show us the code that addes the role, maybe its an easy fix

Yes, that was my idea, maybe someone can fix it. I will try to understand the code and I will post it.

Thank you all for the help.

---

<div class="post-metadata">

**Author:** ![msteele](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/msteele/32/77889_2.png) [@msteele](https://forum.inductiveautomation.com/u/msteele)\
**Post date:** [November 27, 2024, 6:31pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/5 "2024-11-27T18:31:07Z")

</div>

With more things going to text on 8.3, maybe it would be time to consider some sort of source control functionality for the Ignition Exchange. Then others can submit pull requests to fix bugs or add features to existing Exchange resources.

---

<div class="post-metadata">

**Author:** ![Arnaud\_Declerck](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/arnaud_declerck/32/44872_2.png) [@Arnaud\_Declerck](https://forum.inductiveautomation.com/u/Arnaud_Declerck)\
**Post date:** [November 27, 2024, 7:35pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/6 "2024-11-27T19:35:31Z")

</div>

The author of this ressource doesn't work anymore at IA so i don't know if she keep it updated. I use that ressource in our standard project. I will check if we made any update to it but will not be before this weekend.

---

<div class="post-metadata">

**Author:** ![Aiasa21](https://avatars.discourse-cdn.com/v4/letter/a/aeb1de/32.png) [@Aiasa21](https://forum.inductiveautomation.com/u/Aiasa21)\
**Post date:** [November 27, 2024, 7:41pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/7 "2024-11-27T19:41:33Z")

</div>

> [@Arnaud\_Declerck](#):
>
> I will check if we made any update to it but will not be before this weekend.

Don't worry, in fact, thank you for your availability. I find this resource very useful, although I would like to be able to make a few more modifications, such as being able to choose from different icons (currently, it seems there are only the default ones). I would have also added the icons from Bootstrap as I already have in the designer, but I'm not sure how to add them in the resource. Another idea that comes to mind could be to make it possible to grant access (and visibility) only to specific users rather than by role.

This resource is so useful that it would be a shame not to continue with the updates.

Thanks again

---

<div class="post-metadata">

**Author:** ![Aiasa21](https://avatars.discourse-cdn.com/v4/letter/a/aeb1de/32.png) [@Aiasa21](https://forum.inductiveautomation.com/u/Aiasa21)\
**Post date:** [November 29, 2024, 8:39pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/9 "2024-11-29T20:39:54Z")

</div>

I tested the issue with the help of GPT, and here’s what I found:

The error appears in this part of the original code:

The code where the error appears is this:

```python

	def buildInstance(item, options, instances, depth, length, index):
		isChild = True
		selectedValues = []
		subInstances = []
		for roles in range(len(item['requiredRoles'])):
			for opt in range(len(options)):
				if item['requiredRoles'][roles] == options[opt]['label']:
					selectedValues.append(options[opt]['value'])
		if len(item['items']) > 0:
			isChild = False	
			
		#dict matches the view params of Menu Items	
		dict =	{
					"viewName": item['label']['text'], 
					"target": item['target'],
					"dropdownItems": options, 
					"selectedValues": selectedValues,
					"isChild": isChild, 
					"length":length,
					"index":index,
					"depth": depth,
					"tagPath":item['path'],
					"iconPath":item['label']['icon']['path'],
					"requiredRoles": item['requiredRoles']
				}
		instances.append(dict)
					
		if len(item['items']) > 0:
			length = len(item['items'])	
			for j in range(len(item['items'])):
				index = j
				buildInstance(item['items'][j], options, instances, depth+1, length, index)
				

		
	instances = [] 
	items = value
	rolesDict = {}
	roles = system.user.getRoles("default") #get all possible roles from user source
	options= [] #rolesDict will be appended to this to fill in the dropdown options
	depth = 0 
	
	#create dropdown items object
	for r in range(len(roles)):
		rolesDict = {"value":r, "label":roles[r]}
		options.append(rolesDict.copy())
		
	length = len(items)
	for i in range(len(items)):
		index = i
		buildInstance(items[i], options, instances, depth, length, index)
	return instances

```

The error appears in this part of the original code:

```python
for roles in range(len(item['requiredRoles'])):

```

The issue is that when you delete all the roles`item['requiredRoles']` is not an array but a boolean (`False` ). To handle this, I updated the code as follows:

```python
requiredRoles = item.get('requiredRoles', [])
if isinstance(requiredRoles, bool):
    requiredRoles = []  

```

Here’s the final code I tried, and it seems to work:

```python
		
def buildInstance(item, options, instances, depth, length, index):
    isChild = True
    selectedValues = []
    subInstances = []
    
    # Get the required roles
    requiredRoles = item.get('requiredRoles', [])
    if isinstance(requiredRoles, bool):
        requiredRoles = [] # If it's a boolean, set it to an empty list

    # Check if any of the options match the required roles
    for roles in range(len(requiredRoles)):
        for opt in range(len(options)):
            if requiredRoles[roles] == options[opt]['label']:
                selectedValues.append(options[opt]['value'])
    
    # If the item has child items, it's not a leaf (isChild = False)
    if len(item['items']) > 0:
        isChild = False  

    # Create a dictionary matching the view parameters for Menu Items
    dict = {
        "viewName": item['label']['text'],
        "target": item['target'],
        "dropdownItems": options,
        "selectedValues": selectedValues,
        "isChild": isChild,
        "length": length,
        "index": index,
        "depth": depth,
        "tagPath": item['path'],
        "iconPath": item['label']['icon']['path'],
        "requiredRoles": requiredRoles
    }
    instances.append(dict)

    # If the item has sub-items, recursively process them
    if len(item['items']) > 0:
        length = len(item['items'])  
        for j in range(len(item['items'])):
            index = j
            buildInstance(item['items'][j], options, instances, depth + 1, length, index)

# Main function logic
instances = [] 
items = value # Assuming 'value' is already defined somewhere in your code
rolesDict = {}
roles = system.user.getRoles("default") # Get all possible roles from user source
options = [] # rolesDict will be appended to this to fill in the dropdown options
depth = 0

# Create dropdown items object for each role
for r in range(len(roles)):
    rolesDict = {"value": r, "label": roles[r]}
    options.append(rolesDict.copy())

length = len(items)
for i in range(len(items)):
    index = i
    buildInstance(items[i], options, instances, depth, length, index)

return instances

```

Please give me your feedback about this.

Thank you

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [December 2, 2024, 1:58pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/10 "2024-12-02T13:58:57Z")

</div>

I would not expect a recursive function to look this way. There should be a base case which ends the recursion, and any number of other cases that result in a recursive call. There are a number of other things that are generally not pythonic.

1. Anytime you see a for loop with this form: `for x in range(len(y))` it can always be written better as `for x in y`.
2. In the case where you need both the index and the object you can use the built-in `enumerate()` function which returns a list of tuples with the index and the object.
3. In python if you have a for loop that is just appending an item to a list:

```python
for x in y:
    if someCondition:
        someList.append(x)

```

Then you can write that loop as a comprehension:

```python
someList = [x for x in y if someCondition]

```

which may look confusing until you learn to read them, but they have the advantage of being more performant.

1. You should never use built-in names as variable names. Because everything in python is a first class object this: `dict = {key:value}` is actually redefining the `dict()` function to this dictionary. So if you tried something like: `dict = {key:value} dict(someVariable)` the code would fail.
2. IMO passing an object into a function for the sole purpose of modifying that object is a code smell. You may argue that IA does this with many of their functions, but in those cases they are passing in immutable objects and the function is returning a brand new object, not the same object, but "different". Don't get me wrong, this technique has it's place, but I try to avoid it, because I find it hard to follow. This is one of the reasons I avoid transforms in perspective.

All of that said, I would expect your code to look something like this:

```python
    def buildInstance(item, options, depth, length, index):
        isChild = not item['items']
        selectedValues = [opt['value'] for opt in options for role in item[requiredRoles] if role == opt['label']]
 
        # Create a dictionary matching the view parameters for Menu Items
        if isChild:
	        return {
	                "viewName": item['label']['text'],
	                "target": item['target'],
	                "dropdownItems": options,
	                "selectedValues": selectedValues,
	                "isChild": isChild,
	                "length": length,
	                "index": index,
	                "depth": depth,
	                "tagPath": item['path'],
	                "iconPath": item['label']['icon']['path'],
	                "requiredRoles": item['requiredRoles']
	            }

        # If the item has sub-items, recursively process them
        for index,item in enumerate(item['items']):
            return buildInstance(item, options, depth + 1, len(item['items']), index)

# Main function logic
roles = system.user.getRoles("default") # Get all possible roles from user source

# Create dropdown items object for each role
options = [{'value':r, 'label':role} for r,role in enumerate(roles)]
return buildInstance(value, options, 0 , len(value), 0)

```

You will notice that I do not do any checks for the type of `item['requiredRoles']`, this is because I am using it as a list, and if it happens to be a `bool`, the loop `for role in item['requiredRoles']:` will fall through and no items will be appended to `selectedValues`.

You will also note that I do not use the `len()` function to determine if the item is a child or not. If a list has a length of 0, then when examined as a conditional statement it will return false. Otherwise, it will return true. This means that `isChild = not item['items']` will return true if and only if the length of `item['items']` is 0.

In this function, the item being a child item is the base case, so that is where we return the instance, otherwise we call the function recursively.

---

<div class="post-metadata">

**Author:** ![Aiasa21](https://avatars.discourse-cdn.com/v4/letter/a/aeb1de/32.png) [@Aiasa21](https://forum.inductiveautomation.com/u/Aiasa21)\
**Post date:** [December 4, 2024, 5:33pm UTC](https://forum.inductiveautomation.com/t/role-based-menu-navigation-bug/99167/11 "2024-12-04T17:33:58Z")

</div>

Thanks for the reply @lrose, this is really helpful.

I'm going to read calmly and try to implement it!
