List of users with incomplete profiles?

Former Member
Former Member

Is there a way, in version 11, to download or view a list of members who haven't completed parts of their profile? Our use case is employees. Because we ask all employees to complete certain parts of their community profile, I'd like to be able to run an audit to see which employees still need to post, say, a profile pic, or update their title.

I have a vague memory of this being something that is not available in v11 but is in v12...is that correct? Or is there a way to do this in v11 that I just don't know about?

Thanks!

Parents Reply Children
  • Thanks Steven, I'd like to see how that performs. It's not immediately apparent how to work with $core_v2_user.List() .. that returns a PagedList object containing the users.

    One of the methods on there is ForEach, which expects an 'action'.. I don't see any other example code using this, nor GetEnumerator.

    I guess there are a few questions off the back of this..

    1) How do you iterate over all the 'pages' and results in a PagedList

    2) What is the ForEach for, and what 'action' is it expecting as a parameter?

    3) Is GetEnumerator useful for anything?

  • Each call to $core_v2_user.List() will return a single PagedList that contains a page of results based of the PageSize and PageIndex query parameters used. To iterate through all users, you'd need to create a loop around that list call that increments the PageIndex until all results have been read.

    ForEach and GetEnumerator are both different ways of working with each item of the list in code... when working with PagedList in widgets, it's best to use the Velocity #foreach syntax instead of these methods. That said, sample ForEach usage in C# would look something like:

    PagedList<User> users = Apis.Get<IUsers>().List(new UsersListOptions {PageIndex = 0, PageSize = 100});
    users.ForEach(user =>
    {
    	// perform an action on each user
    });

  • Thanks Steven. I've put some code together in Velocity to loop over the pages & the users in each page...

    #set($pageSize = 100)
    ## Find out how many users we're dealing with
    #set($users = $core_v2_user.List("%{ PageIndex = 0, PageSize = $pageSize }"))
    ## $users
    
    ## How many pages of $pageSize do we need to iterate over?
    #set($pages = $users.TotalCount / $pageSize)
    #set($pageLeftOver = $users.TotalCount % $pageSize)
    #if ($pageLeftOver>0) #set($pages = $pages+1) #end
    #set($pages = $pages-1) ## We're working with 0 based index
    
    #set($count = 0)
    
    #set($pages = 200) ## OVERRIDE
    
    ## Loop over pages of users
    #foreach ($number in [0..$pages])
        ## Get the users for a page
        #set($users = $core_v2_user.List("%{ PageIndex = $number, PageSize = $pageSize }"))
        ## Loop over a page of users
        #foreach ($user in $users)
            #set($count = $count+1)
            ## $user.Id<br>
        #end
    #end
    
    $count

    The performance isn't great, and it gets to a point where it generates a system error..

    System.Threading.ThreadAbortException: Thread was being aborted.
       at SNIReadSyncOverAsync(SNI_ConnWrapper* , SNI_Packet** , Int32 )
       at SNINativeMethodWrapper.SNIReadSyncOverAsync(SafeHandle pConn, IntPtr& packet, Int32 timeout)
       at System.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
       at System.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
       at System.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer()
       at System.Data.SqlClient.TdsParserStateObject.TryReadByteArray(Byte[] buff, Int32 offset, Int32 len, Int32& totalRead)
       at System.Data.SqlClient.TdsParserStateObject.TryReadString(Int32 length, String& value)
       at System.Data.SqlClient.TdsParser.TryReadSqlStringValue(SqlBuffer value, Byte type, Int32 length, Encoding encoding, Boolean isPlp, TdsParserStateObject stateObj)
       at System.Data.SqlClient.TdsParser.TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, Int32 length, TdsParserStateObject stateObj, SqlCommandColumnEncryptionSetting columnEncryptionOverride, String columnName)
       at System.Data.SqlClient.SqlDataReader.TryReadColumnInternal(Int32 i, Boolean readHeaderOnly)
       at System.Data.SqlClient.SqlDataReader.TryReadColumn(Int32 i, Boolean setTimeout, Boolean allowPartiallyReadColumn)
       at System.Data.SqlClient.SqlDataReader.GetString(Int32 i)
       at Telligent.Evolution.Components.CommonDataProvider.PopulateUserFromIDataReader(IDataReader reader, Boolean isEditable)
       at Telligent.Evolution.Data.SqlCommonDataProvider.GetUserList(UserQuery query, Int32& totalRecords)
       at Telligent.Evolution.Components.UserDataService.GetUsers(UserQuery query, Boolean useCache)
       at Telligent.Evolution.Components.UserDataService.GetUsers(UserQuery query)
       at Telligent.Evolution.Components.UserService.GetUsers(UserQuery query)
       at Telligent.Evolution.Api.Services.UserService.GetUsers(String[] usernames, List`1 contentIds, String emailAddress, Nullable`1 joinDate, Nullable`1 roleId, Nullable`1 lastUpdatedUtcDate, Boolean includeHidden, Nullable`1 pageSize, Nullable`1 pageIndex, String sortBy, String sortOrder, String accountStatus, String presence)
       at Telligent.Evolution.Extensibility.Api.Version1.Users.List(UsersListOptions options)

    At first I was working in the Script Sandbox, but the performance in there is terrible for bringing back large numbers of objects.. it's a lot better in a widget, but even then, the following code takes about a minute for 10,000 users.

    I needed to put an override in on line 14 so that it didn't error out.. beyond maybe 20k users it stops returning anything & times out.

    Although we might get better results from doing this in a plugin, it doesn't make it easy for us to run an ad-hoc check of users accounts with a certain profile field set, etc.

    What we're now thinking of doing is running a data extract & transformation on the profile field XML (in the d/b) and periodically storing that in another table with the data in a more query-able form for SQL queries to run against it.

    However, I think it could be worth the product team looking at how the core platform could better support this type of operation.. unless I'm missing an approach which has better performance than what I've covered here?

  • Pulling direct from SQL is not an extensible/upgrade-safe/supported way to do this. It does sound like a much better candidate for a custom plugin-based job to pre-calculate and store in a custom table, rather than executing ad hoc in a widget. The same job plugin could also capture User.AfterCreate/AfterUpdate events to update its records when profile fields changed.

    In terms of core platform support... we're always open to suggestions. That said, if the end goal is to ensure user profile fields are filled out, there are several other ways to potentially do this. One option specifically for new users is to edit the configuration of the "User - Login and Create" widget on the "User Registration" page to include (and require) certain profile fields be filled out on registration. Alternatively, a custom widget could be created that would show each user a selection of profile fields that they haven't filled out and encourage them to fill them out. (This would reduce the load of calculating all users at once).

  • Here's an idea off the back of this thread Slight smile