I'd like to know how to do this too - see How to use the Feature Management App (FMA) to grant access for a defined number of users?
What I plan to do if nothing better comes up, is have a protected list custom setting holding the User Id as a text string plus the feature name as a text string. The presence of a record means access is granted and the lack of a record means it isn't.
As far as the allocation goes, I intend for it to be first come, first served. If the available seats are all gone when a user tries to access a feature, they will either get an error message or the feature may just not appear in their UI.
Something like this where FeatureAssignment__c
is the protected list custom setting:
// Worth caching if multiple check calls in a transaction
private static final Map<String, Boolean> cache = new Map<String, Boolean>();
public static Boolean checkFeatureAvailable(String featureName) {
Boolean available = cache.get(featureName);
if (available != null) {
return available;
} else {
Boolean assigned = queryFeatureAvailable(featureName);
cache.put(featureName, assigned);
return assigned;
}
}
// Adds a query so can't precede a callout
private static Boolean queryFeatureAvailable(String featureName) {
Boolean assigned = [
select count()
from FeatureAssignment__c
where UserId__c = :UserInfo.getUserId()
and FeatureName__c = :featureName
] > 0;
if (assigned) return true;
else return assignFeature(featureName);
}
// Adds an additional query and a DML
private static Boolean assignFeature(String featureName) {
Integer available = FeatureManagement.checkPackageIntegerValue(featureName);
Integer used = [
select count()
from FeatureAssignment__c
and FeatureName__c = :featureName
];
if (used < available) {
insert new FeatureAssignment__c(
Name = ...,
UserId__c = UserInfo.getUserId(),
FeatureName__c = featureName
);
return true;
} else {
return false;
}
}
plus a trigger to return access that is no longer being used to the available pool:
trigger UserTrigger on User (after update) {
Set<Id> deactivatedUserIds = new Set<Id>();
for (User u : Trigger.new) {
User old = Trigger.oldMap.get(u.Id);
if (old.IsActive && !u.IsActive) {
deactivatedUserIds.add(u.Id);
}
}
if (deactivatedUserIds.size() > 0) {
// Free up the assignment (if any)
delete [
select Id
from FeatureAssignment__c
where UserId__c in :deactivatedUserIds
];
}
}
This also allows an admin to free up the feature assignment by de-activating and re-activating the user.