Get members in multiple groups

Certified Senior Developer

I'm trying to think my way through a problem. What I need is, given a list of groups, I want to see a list of users that are members of all of those groups. The existing tools almost, but not quite do what I need. 

groupMembers() will give me the members of a single group, so I thought maybe if I got the members of each group and got the union() of them, that would solve my problem, but union only takes 2 lists and looping through with foreach  (I do so wish there were other types of loops!) would mean somehow getting the union of each member of the list, then unioning those results until I get down to just one list, but that sounds like a pain to code and probably not terribly efficient. 

isMemberOfGroups() is nice in that I can take a user and see if they are a member of all of a list of groups, but then I have to loop through basically all users (or at least all users in some initial group) and ultimately I am not sure how many users there could be or whether the maximum batch size of 10,000 will be enough. That doesn't seem like a whole lot of users to ultimately have as a limitation. 

Then there's getdistinctusers() which is kind of the opposite of what I want? It gives me all users across a set of groups with no duplicates, I think?

Are one of these methods the way to go? Am I missing something? 

  Discussion posts and replies are publicly visible

Parents Reply Children
  • 0
    Certified Lead Developer
    in reply to Marco

    reduce() is so convoluted to use that it may end up being quite a bit easier to develop a recursive "intersection" handler function.  There might be a way to do it without, but so far I'm unclear how exactly it would work.

  • 0
    Certified Lead Developer
    in reply to Marco

    I was able to cook up this funcitonal workaround that sidesteps Appian's nearly-impossibly-sloppy handling of arrays-of-arrays that seem to want to actively prevent using intersection() inside reduce().  It does require creating a sub-rule, but allows us to use the inherent recursive functionality of reduce(), and not need to use our own recursion which is always riskier.

    parent rule:

    a!localVariables(
      
      local!allUsers: a!forEach(
        ri!groups,
        joinarray(
          a!groupMembers(
            group: fv!item,
            direct: true(),
            memberType: "USER"
          ).data,
          ";"
        )
      ),
    
      reduce(
        rule!TEST_manualIntersection,
        local!allUsers[1],
        ldrop(local!allUsers, 1)
      )
    )

    Sub rule (test_manualIntersection):

    intersection(
      split(ri!textList1, ";"),
      split(ri!textList2, ";")
    )
    
    /* both RIs are non-array text variables */

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    hmm, almost works. I ran it on a set of groups with 2 members common among them and only got 1 of those two members back. All the other members were filtered out. I've been trying to get reduce to work, and having no luck there. 

  • 0
    Certified Senior Developer
    in reply to Marco

    seems to return only 1 no matter how many common users there are as far as I can tell. 

    It seems to just grab the first user it encounters that exists in all the groups

  • 0
    Certified Lead Developer
    in reply to Marco

    urgh.  that happens for me too now that i test it more broadly.

    the only other thing i can think of is to abandon intersection() and reduce() entirely and implement your own stuff.  the steps i'd thought through earlier would be along the lines of this:

    1) iterate over all groups grabbing all members

    2) make a single array comprising all unique usernames among all groups

    3) iterate over the list of all usernames and for each one, loop over all original group lists checking that the current user is in all of them (if not, discard)

    4) you should now end up with a list of all usernames in all groups provided.

    This would likely get a *little* processing-intensive if you feed in an obscenely large number of groups, but then again the previous solution probably would too.

  • +1
    Certified Lead Developer
    in reply to Marco

    try this out:

    a!localVariables(
      
      local!allUsers: a!forEach(
        ri!groups,
        a!groupMembers(
          group: fv!item,
          direct: true(),
          memberType: "USER"
        ).data
      ),
        
      local!uniqueUsers: rule!GCO_RULE_General_distinct(  /* helper rule that unions the input array against itself */
        touniformstring(a!flatten(local!allUsers))
      ),
      
      
      a!flatten(
        a!forEach(
          local!uniqueUsers,
          
          a!localVariables(
            local!currentUser: fv!item,
            if(
              and(
                a!forEach(
                  local!allUsers,
                  contains(touniformstring(fv!item), local!currentUser)
                )
              ),
              local!currentUser,
              {}
            )
          )
        )
      )
    )

  • +1
    Certified Senior Developer
    in reply to Mike Schmitt

    I was thinking of something like this, but I am not entirely sure how to use all() with contains(). It's so hard getting appian to do what you want it to do when you're used to programming in other languages. Let me try the second one 

    a!localVariables(
      local!allUsers: a!forEach(
        items: ri!groupList,
        expression: a!groupMembers(
          group: fv!item,
          direct: true(),
          memberType: "USER"
        ).data
      ),
      local!uniqueUsers: a!forEach(
        items: local!allUsers[1],
        expression: if(
          all(fn!contains, local!allUsers, fv!item),
          fv!item,
          null()
        )
      ),
      local!uniqueUsers
    )

  • 0
    Certified Lead Developer
    in reply to Marco

    The example I posted above should be working, FWIW.  I don't think "all()" is needed.

  • 0
    Certified Senior Developer
    in reply to Mike Schmitt

    Almost works, but if we don't have any valid users across those groups, I'm getting a big list of empty lists. 

    So if I have the following groups:

    {a,b,c}

    {b,c,d}

    {z,y,x}

    {a,b,c,d}

    the result I'm getting is something like {{},{},{},{},{},{},{}} (I'm assuming it's one for each unique value, but can't tell for sure)

  • 0
    Certified Lead Developer
    in reply to Marco

    that's a known side-effect of a!forEach when it returns all empty lists.  that can be fixed by wrapping the whole enchilada in a!flatten.  i'll update my example above to reflect this.