├── .gitignore ├── pf-app └── main │ └── default │ ├── classes │ ├── FeatureAccessException.cls │ ├── FeatureConsoleAPI.cls-meta.xml │ ├── FeatureConsoleTest.cls-meta.xml │ ├── ProjectExtension.cls-meta.xml │ ├── FeatureAccessException.cls-meta.xml │ ├── FeatureConsoleController.cls-meta.xml │ ├── FeatureConsoleControllerTest.cls-meta.xml │ ├── ProjectExtension.cls │ ├── FeatureConsoleController.cls │ ├── FeatureConsoleControllerTest.cls │ ├── FeatureConsoleAPI.cls │ └── FeatureConsoleTest.cls │ ├── triggers │ ├── CountTrigger.trigger-meta.xml │ └── CountTrigger.trigger │ ├── tabs │ ├── BudgetLineItem__c.tab-meta.xml │ ├── CostCenter__c.tab-meta.xml │ ├── OrganizationBudget__c.tab-meta.xml │ ├── Projects.tab-meta.xml │ └── FeatureConsole.tab-meta.xml │ ├── objects │ ├── CostCenter__c │ │ ├── listViews │ │ │ └── All.listView-meta.xml │ │ └── CostCenter__c.object-meta.xml │ ├── OrganizationBudget__c │ │ ├── listViews │ │ │ └── All.listView-meta.xml │ │ └── OrganizationBudget__c.object-meta.xml │ ├── Project__c │ │ ├── fields │ │ │ ├── ExpenseTracking__c.field-meta.xml │ │ │ └── TotalBudget__c.field-meta.xml │ │ └── Project__c.object-meta.xml │ └── BudgetLineItem__c │ │ ├── fields │ │ └── BudgetLineItems__c.field-meta.xml │ │ └── BudgetLineItem__c.object-meta.xml │ ├── pages │ ├── Projects.page-meta.xml │ ├── Feature_Console.page-meta.xml │ ├── Feature_Console.page │ └── Projects.page │ ├── featureParameters │ ├── MaxBudgetDate.featureParameterDate-meta.xml │ ├── MaxOpenProjects.featureParameterInteger-meta.xml │ ├── ActualOpenProjects.featureParameterInteger-meta.xml │ ├── CurrentProjectCount.featureParameterInteger-meta.xml │ ├── ActualLatestBudget.featureParameterDate-meta.xml │ ├── BudgetTrackingEnabled.featureParameterBoolean-meta.xml │ ├── ExpenseTrackingEnabled.featureParameterBoolean-meta.xml │ ├── BudgetTrackingPermitted.featureParameterBoolean-meta.xml │ └── ExpenseTrackingPermitted.featureParameterBoolean-meta.xml │ ├── applications │ └── ProjectForce.app-meta.xml │ ├── layouts │ ├── BudgetLineItem__c-Budget Line Items Layout.layout-meta.xml │ ├── CostCenter__c-Cost Center Layout.layout-meta.xml │ ├── OrganizationBudget__c-Organization Budget Layout.layout-meta.xml │ └── Project__c-Project Layout.layout-meta.xml │ └── profiles │ ├── Custom%3A Sales Profile.profile-meta.xml │ ├── Custom%3A Marketing Profile.profile-meta.xml │ ├── Custom%3A Support Profile.profile-meta.xml │ └── Admin.profile-meta.xml ├── CODEOWNERS ├── config └── project-scratch-def.json ├── sfdx-project.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .sfdx 2 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureAccessException.cls: -------------------------------------------------------------------------------- 1 | public class FeatureAccessException extends Exception { 2 | 3 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Your Company", 3 | "edition": "Developer", 4 | "orgPreferences" : { 5 | "enabled": ["S1DesktopEnabled"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "pf-app", 5 | "default": true 6 | } 7 | ], 8 | "sfdcLoginUrl": "https://login.salesforce.com", 9 | "sourceApiVersion": "44.0" 10 | } 11 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleAPI.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/ProjectExtension.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureAccessException.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/triggers/CountTrigger.trigger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 39.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 44.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/tabs/BudgetLineItem__c.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Custom41: Stack of Cash 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/tabs/CostCenter__c.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Custom57: Building Block 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/tabs/OrganizationBudget__c.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | Custom71: Headset 5 | 6 | -------------------------------------------------------------------------------- /pf-app/main/default/tabs/Projects.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom63: Chip 5 | Projects 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/CostCenter__c/listViews/All.listView-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | Everything 5 | 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/tabs/FeatureConsole.tab-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Custom57: Building Block 5 | Feature_Console 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/OrganizationBudget__c/listViews/All.listView-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | All 4 | Everything 5 | 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/pages/Projects.page-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 37.0 4 | false 5 | false 6 | 7 | 8 | -------------------------------------------------------------------------------- /pf-app/main/default/triggers/CountTrigger.trigger: -------------------------------------------------------------------------------- 1 | trigger CountTrigger on Project__c (after insert) { 2 | // trigger to update the CurrentProjectCount integer param each time a Project object is created. 3 | integer i = FeatureManagement.checkPackageIntegerValue('CurrentProjectCount'); 4 | i = i + 1; 5 | FeatureConsoleApi.updateProjectCount(i); 6 | } -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/MaxBudgetDate.featureParameterDate-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LmoToSubscriber 4 | Latest Budget Date 5 | 1970-01-01 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/pages/Feature_Console.page-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 37.0 4 | false 5 | false 6 | 7 | 8 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/MaxOpenProjects.featureParameterInteger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LmoToSubscriber 4 | Maximum Open Projects 5 | 0 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/ActualOpenProjects.featureParameterInteger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubscriberToLmo 4 | Actual Open Projects 5 | 0 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/CurrentProjectCount.featureParameterInteger-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubscriberToLmo 4 | Current Project Count 5 | 0 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/ActualLatestBudget.featureParameterDate-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubscriberToLmo 4 | Actual Latest Budget Date 5 | 1970-01-01 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/BudgetTrackingEnabled.featureParameterBoolean-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubscriberToLmo 4 | Budget Tracking Enabled 5 | false 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/ExpenseTrackingEnabled.featureParameterBoolean-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SubscriberToLmo 4 | Expense Tracking Enabled 5 | false 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/BudgetTrackingPermitted.featureParameterBoolean-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LmoToSubscriber 4 | Budget Tracking Permitted 5 | false 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/featureParameters/ExpenseTrackingPermitted.featureParameterBoolean-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LmoToSubscriber 4 | Expense Tracking Permitted 5 | false 6 | 7 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/Project__c/fields/ExpenseTracking__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ExpenseTracking__c 4 | false 5 | false 6 | false 7 | 8 | false 9 | Checkbox 10 | 11 | -------------------------------------------------------------------------------- /pf-app/main/default/applications/ProjectForce.app-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | standard-home 4 | Large 5 | 6 | Projects 7 | FeatureConsole 8 | CostCenter__c 9 | BudgetLineItem__c 10 | OrganizationBudget__c 11 | 12 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/Project__c/fields/TotalBudget__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | TotalBudget__c 4 | false 5 | false 6 | 7 | 18 8 | false 9 | 0 10 | false 11 | Number 12 | false 13 | 14 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/BudgetLineItem__c/fields/BudgetLineItems__c.field-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | BudgetLineItems__c 4 | false 5 | 6 | Project__c 7 | Budget_Line_Items 8 | 0 9 | false 10 | false 11 | MasterDetail 12 | false 13 | 14 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/ProjectExtension.cls: -------------------------------------------------------------------------------- 1 | public class ProjectExtension { 2 | 3 | public Boolean expenseTrackingEnabled; 4 | public Boolean budgetTrackingEnabled; 5 | 6 | public ProjectExtension(ApexPages.StandardController standardController) { 7 | this.expenseTrackingEnabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 8 | this.budgetTrackingEnabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 9 | } 10 | 11 | public ProjectExtension(ApexPages.StandardSetController controller) { 12 | this.expenseTrackingEnabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 13 | this.budgetTrackingEnabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 14 | } 15 | 16 | public Boolean getExpenseTrackingEnabled() { 17 | return this.expenseTrackingEnabled; 18 | } 19 | 20 | public Boolean getBudgetTrackingEnabled() { 21 | return this.budgetTrackingEnabled; 22 | } 23 | } -------------------------------------------------------------------------------- /pf-app/main/default/pages/Feature_Console.page: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pf-app/main/default/pages/Projects.page: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | Edit 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Salesforce.com, inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /pf-app/main/default/layouts/BudgetLineItem__c-Budget Line Items Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | true 7 | 8 | 9 | 10 | Required 11 | Name 12 | 13 | 14 | Required 15 | BudgetLineItems__c 16 | 17 | 18 | 19 | 20 | 21 | 22 | false 23 | false 24 | true 25 | 26 | 27 | 28 | Readonly 29 | CreatedById 30 | 31 | 32 | 33 | 34 | Readonly 35 | LastModifiedById 36 | 37 | 38 | 39 | 40 | 41 | false 42 | false 43 | true 44 | 45 | 46 | 47 | 48 | 49 | false 50 | false 51 | false 52 | false 53 | false 54 | 55 | -------------------------------------------------------------------------------- /pf-app/main/default/layouts/CostCenter__c-Cost Center Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | true 7 | 8 | 9 | 10 | Required 11 | Name 12 | 13 | 14 | 15 | 16 | Edit 17 | OwnerId 18 | 19 | 20 | 21 | 22 | 23 | false 24 | false 25 | true 26 | 27 | 28 | 29 | Readonly 30 | CreatedById 31 | 32 | 33 | 34 | 35 | Readonly 36 | LastModifiedById 37 | 38 | 39 | 40 | 41 | 42 | false 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | false 51 | false 52 | false 53 | false 54 | false 55 | 56 | -------------------------------------------------------------------------------- /pf-app/main/default/layouts/OrganizationBudget__c-Organization Budget Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | true 7 | 8 | 9 | 10 | Required 11 | Name 12 | 13 | 14 | 15 | 16 | Edit 17 | OwnerId 18 | 19 | 20 | 21 | 22 | 23 | false 24 | false 25 | true 26 | 27 | 28 | 29 | Readonly 30 | CreatedById 31 | 32 | 33 | 34 | 35 | Readonly 36 | LastModifiedById 37 | 38 | 39 | 40 | 41 | 42 | false 43 | false 44 | true 45 | 46 | 47 | 48 | 49 | 50 | false 51 | false 52 | false 53 | false 54 | false 55 | 56 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleController.cls: -------------------------------------------------------------------------------- 1 | public class FeatureConsoleController { 2 | 3 | // controller class for the feature console tab/page 4 | 5 | List features; 6 | String ET_FEATURE = 'Expense Tracking'; 7 | String BT_FEATURE = 'Budget Tracking'; 8 | 9 | // check the status of the expense/budget tracking features when the page loads. 10 | public FeatureConsoleController() { 11 | features = new List(); 12 | features.add(new Feature(FeatureConsoleAPI.expenseTrackingEnabled(21367), ET_FEATURE)); 13 | features.add(new Feature(FeatureConsoleAPI.budgetTrackingEnabled(21367), BT_FEATURE)); 14 | } 15 | 16 | // simple sub-class to represent the status of a given feature. 17 | public class Feature { 18 | public Boolean enabled {get; set;} 19 | public String name{get;set;} 20 | public Feature(boolean enabled, String name) { 21 | this.enabled = enabled; 22 | this.name = name; 23 | } 24 | } 25 | 26 | // accessor method for the VF page to get the current features. 27 | public List getFeatures() { 28 | return features; 29 | } 30 | 31 | 32 | // when the page saves, enable/disable features based on how they're set on the page. 33 | public void save() { 34 | for (Feature feature : features) { 35 | if(feature.enabled) { 36 | try { 37 | if(feature.name.equals(ET_FEATURE)) { 38 | FeatureConsoleAPI.enableExpenseTracking(21367); 39 | } 40 | else if (feature.name.equals(BT_FEATURE)) { 41 | FeatureConsoleAPI.enableBudgetTracking(21367); 42 | } 43 | } 44 | catch(FeatureAccessException e) { 45 | feature.enabled = false; 46 | ApexPages.addMessages(e); 47 | } 48 | } 49 | else { 50 | if(feature.name.equals(ET_FEATURE)) { 51 | FeatureConsoleAPI.disableExpenseTracking(21367); 52 | } 53 | else if (feature.name.equals(BT_FEATURE)) { 54 | FeatureConsoleAPI.disableBudgetTracking(21367); 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /pf-app/main/default/objects/BudgetLineItem__c/BudgetLineItem__c.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | List 25 | Default 26 | 27 | 28 | New 29 | Default 30 | 31 | 32 | SaveEdit 33 | Default 34 | 35 | 36 | Tab 37 | Default 38 | 39 | 40 | View 41 | Default 42 | 43 | false 44 | SYSTEM 45 | Deployed 46 | false 47 | true 48 | false 49 | false 50 | false 51 | false 52 | true 53 | true 54 | 55 | 56 | 57 | Text 58 | 59 | Budget Line Items 60 | 61 | ControlledByParent 62 | Protected 63 | 64 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/CostCenter__c/CostCenter__c.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | List 25 | Default 26 | 27 | 28 | New 29 | Default 30 | 31 | 32 | SaveEdit 33 | Default 34 | 35 | 36 | Tab 37 | Default 38 | 39 | 40 | View 41 | Default 42 | 43 | false 44 | SYSTEM 45 | Deployed 46 | false 47 | true 48 | false 49 | false 50 | false 51 | true 52 | true 53 | true 54 | 55 | en_US 56 | 57 | 58 | Text 59 | 60 | Cost Centers 61 | 62 | ReadWrite 63 | Public 64 | 65 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/Project__c/Project__c.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | List 25 | Default 26 | 27 | 28 | New 29 | Default 30 | 31 | 32 | SaveEdit 33 | Default 34 | 35 | 36 | Tab 37 | Default 38 | 39 | 40 | View 41 | Default 42 | 43 | false 44 | SYSTEM 45 | Deployed 46 | false 47 | false 48 | true 49 | false 50 | false 51 | false 52 | true 53 | true 54 | true 55 | 56 | en_US 57 | 58 | 59 | Text 60 | 61 | Projects 62 | 63 | ReadWrite 64 | Public 65 | 66 | -------------------------------------------------------------------------------- /pf-app/main/default/objects/OrganizationBudget__c/OrganizationBudget__c.object-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Accept 5 | Default 6 | 7 | 8 | CancelEdit 9 | Default 10 | 11 | 12 | Clone 13 | Default 14 | 15 | 16 | Delete 17 | Default 18 | 19 | 20 | Edit 21 | Default 22 | 23 | 24 | List 25 | Default 26 | 27 | 28 | New 29 | Default 30 | 31 | 32 | SaveEdit 33 | Default 34 | 35 | 36 | Tab 37 | Default 38 | 39 | 40 | View 41 | Default 42 | 43 | false 44 | SYSTEM 45 | Deployed 46 | false 47 | true 48 | false 49 | false 50 | false 51 | true 52 | true 53 | true 54 | 55 | en_US 56 | 57 | 58 | Text 59 | 60 | Organization Budgets 61 | 62 | ReadWrite 63 | Protected 64 | 65 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleControllerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class FeatureConsoleControllerTest { 3 | 4 | static testMethod void testController() { 5 | resetParams(); 6 | 7 | // create an instance of the controller and retrieve the list of Feature objects 8 | FeatureConsoleController controller = new FeatureConsoleController(); 9 | List features = controller.getFeatures(); 10 | // assert that there are 2 features 11 | System.assertEquals(2, features.size(), 'Unexpected number of features.'); 12 | 13 | // test that we cannot currently enable either feature 14 | for(FeatureConsoleController.Feature feature:features) { 15 | feature.enabled = true; 16 | } 17 | // attempt save our changes in the controller 18 | controller.save(); 19 | // verify that both features are still disabled 20 | boolean etEnabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 21 | boolean btEnabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 22 | System.assert(!etEnabled, 'Expense Tracking feature is enabled and should not be.'); 23 | System.assert(!btEnabled, 'Budget Tracking feature is enabled and should not be.'); 24 | 25 | // turn on the gating param for the expense tracking feature 26 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingPermitted', true); 27 | // now enable the feature and save the controller 28 | for(FeatureConsoleController.Feature feature:features) { 29 | if(feature.name.equals('Expense Tracking')) { 30 | feature.enabled = true; 31 | } 32 | } 33 | controller.save(); 34 | 35 | // verify that the expense tracking feature is enabled, but not the budget tracking feature 36 | etEnabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 37 | btEnabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 38 | System.assert(etEnabled, 'Expense Tracking feature is disabled and should not be.'); 39 | System.assert(!btEnabled, 'Budget Tracking feature is enabled and should not be.'); 40 | } 41 | 42 | private static void resetParams() { 43 | // turn off the gating feature params, just in case they're turned on. 44 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingPermitted', false); 45 | FeatureManagement.setPackageBooleanValue('BudgetTrackingPermitted', false); 46 | } 47 | } -------------------------------------------------------------------------------- /pf-app/main/default/layouts/Project__c-Project Layout.layout-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Submit 4 | 5 | false 6 | false 7 | true 8 | 9 | 10 | 11 | Required 12 | Name 13 | 14 | 15 | Edit 16 | ExpenseTracking__c 17 | 18 | 19 | Edit 20 | TotalBudget__c 21 | 22 | 23 | 24 | 25 | Edit 26 | OwnerId 27 | 28 | 29 | 30 | 31 | 32 | false 33 | false 34 | true 35 | 36 | 37 | 38 | Readonly 39 | CreatedById 40 | 41 | 42 | 43 | 44 | Readonly 45 | LastModifiedById 46 | 47 | 48 | 49 | 50 | 51 | false 52 | false 53 | false 54 | 55 | 56 | 57 | 58 | 59 | 60 | NAME 61 | BudgetLineItem__c.BudgetLineItems__c 62 | 63 | false 64 | false 65 | false 66 | false 67 | false 68 | 69 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleAPI.cls: -------------------------------------------------------------------------------- 1 | global class FeatureConsoleAPI { 2 | 3 | // called when budget tracking feature is enabled from the feature console page. 4 | global static void enableBudgetTracking(integer magic) { 5 | // since this class is global, this is just a means of making sure we are who we say we are. 6 | // this is by no means the gold standard of doing this, and we expect you'll come up with 7 | // something better. 8 | if (magic != 21367) { 9 | throw new FeatureAccessException('Incorrect Magic Number'); 10 | } 11 | // check to see if the feature parameter boolean gating the budget tracking feature 12 | // has been turned on by the LMO. if not, throw an exception. 13 | if (!FeatureManagement.checkPackageBooleanValue('BudgetTrackingPermitted')) { 14 | throw new FeatureAccessException('Budget Tracking Feature not currently licensed'); 15 | } 16 | // de-protect the BudgetLineItem custom object, which will also make its custom tab visible. 17 | FeatureManagement.changeProtection('BudgetLineItem__c', 'CustomObject', 'Unprotected'); 18 | // set the BudgetTrackingEnabled feature param to true. this value reports back to the LMO 19 | // so that we know which features are enabled/disabled. it also controls whether or not 20 | // the Total Budget column is visible on the Projects tab/page. 21 | FeatureManagement.setPackageBooleanValue('BudgetTrackingEnabled', true); 22 | } 23 | 24 | // called when the budget tracking feature is disabled from the feature console page 25 | global static void disableBudgetTracking(integer magic) { 26 | // since this class is global, this is just a means of making sure we are who we say we are. 27 | // this is by no means the gold standard of doing this, and we expect you'll come up with 28 | // something better. 29 | if (magic != 21367) { 30 | throw new FeatureAccessException('Incorrect Magic Number'); 31 | } 32 | // note that we do NOT re-protect the BudgetLineItem object because this is not allowed. 33 | // set the BudgetTrackingEnabled feature param back to false so that we know in the LMO 34 | // that this feature is currently disabled in the subscriber org. this will also hide 35 | // the Total Budget column on the Projects tab/page. 36 | FeatureManagement.setPackageBooleanValue('BudgetTrackingEnabled', false); 37 | } 38 | 39 | // accessor method to determine if the budget tracking feature is turned on 40 | global static boolean budgetTrackingEnabled(integer magic) { 41 | // since this class is global, this is just a means of making sure we are who we say we are. 42 | // this is by no means the gold standard of doing this, and we expect you'll come up with 43 | // something better. 44 | if (magic != 21367) { 45 | throw new FeatureAccessException('Incorrect Magic Number'); 46 | } 47 | return FeatureManagement.checkPackageBooleanValue('BudgetTrackingEnabled') && FeatureManagement.checkPackageBooleanValue('BudgetTrackingPermitted'); 48 | } 49 | 50 | // called when the expense tracking feature is enabled on the feature console page 51 | global static void enableExpenseTracking(integer magic) { 52 | // since this class is global, this is just a means of making sure we are who we say we are. 53 | // this is by no means the gold standard of doing this, and we expect you'll come up with 54 | // something better. 55 | if (magic != 21367) { 56 | throw new FeatureAccessException('Incorrect Magic Number'); 57 | } 58 | // check to see if the feature parameter boolean gating the expense tracking feature 59 | // has been turned on by the LMO. if not, throw an exception. 60 | if (!FeatureManagement.checkPackageBooleanValue('ExpenseTrackingPermitted')) { 61 | throw new FeatureAccessException('Expense Tracking Feature not currently licensed'); 62 | } 63 | // set the ExpenseTrackingEnabled boolean param to true so we'll know in the LMO 64 | // if this feature is turned on. this will also make the Expense Tracking column 65 | // visible on the Projects tab/page. 66 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingEnabled', true); 67 | } 68 | 69 | // accessor method to determine if the expense tracking feature is enabled. 70 | global static boolean expenseTrackingEnabled(integer magic) { 71 | // since this class is global, this is just a means of making sure we are who we say we are. 72 | // this is by no means the gold standard of doing this, and we expect you'll come up with 73 | // something better. 74 | if (magic != 21367) { 75 | throw new FeatureAccessException('Incorrect Magic Number'); 76 | } 77 | return FeatureManagement.checkPackageBooleanValue('ExpenseTrackingEnabled') && FeatureManagement.checkPackageBooleanValue('ExpenseTrackingPermitted'); 78 | } 79 | 80 | // called when the expense tracking feature is disabled on the feature console page 81 | global static void disableExpenseTracking(integer magic) { 82 | // since this class is global, this is just a means of making sure we are who we say we are. 83 | // this is by no means the gold standard of doing this, and we expect you'll come up with 84 | // something better. 85 | if (magic != 21367) { 86 | throw new FeatureAccessException('Incorrect Magic Number'); 87 | } 88 | // set the ExpenseTrackingEnabled boolean param to false so we'll know in the LMO 89 | // that this feature is not currently enabled in the subscriber. this will also hide 90 | // the Expense Tracking column on the Projects tab/page. 91 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingEnabled', false); 92 | } 93 | 94 | // called to update the CurrentProjectCount integer param. this param reports back to the 95 | // LMO and provides information on how many Project objects have been created. 96 | public @future static void updateProjectCount(integer newCount) { 97 | FeatureManagement.setPackageIntegerValue('CurrentProjectCount', newCount); 98 | } 99 | } -------------------------------------------------------------------------------- /pf-app/main/default/profiles/Custom%3A Sales Profile.profile-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectForce 5 | false 6 | false 7 | 8 | 9 | FeatureAccessException 10 | false 11 | 12 | 13 | FeatureConsoleAPI 14 | false 15 | 16 | 17 | FeatureConsoleController 18 | false 19 | 20 | true 21 | 22 | false 23 | Project__c.ExpenseTracking__c 24 | false 25 | 26 | 27 | false 28 | Project__c.TotalBudget__c 29 | false 30 | 31 | 32 | BudgetLineItem__c-Budget Line Items Layout 33 | 34 | 35 | CostCenter__c-Cost Center Layout 36 | 37 | 38 | OrganizationBudget__c-Organization Budget Layout 39 | 40 | 41 | Project__c-Project Layout 42 | 43 | 44 | Feature_Console 45 | false 46 | 47 | 48 | Projects 49 | false 50 | 51 | 52 | BudgetLineItem__c 53 | DefaultOn 54 | 55 | Salesforce 56 | 57 | true 58 | AllowViewKnowledge 59 | 60 | 61 | true 62 | ApiEnabled 63 | 64 | 65 | true 66 | AssignTopics 67 | 68 | 69 | true 70 | ChatterInternalUser 71 | 72 | 73 | true 74 | ChatterInviteExternalUsers 75 | 76 | 77 | true 78 | ChatterOwnGroups 79 | 80 | 81 | true 82 | ConvertLeads 83 | 84 | 85 | true 86 | CreateCustomizeFilters 87 | 88 | 89 | true 90 | CreateCustomizeReports 91 | 92 | 93 | true 94 | CreateTopics 95 | 96 | 97 | true 98 | DistributeFromPersWksp 99 | 100 | 101 | true 102 | EditEvent 103 | 104 | 105 | true 106 | EditOppLineItemUnitPrice 107 | 108 | 109 | true 110 | EditTask 111 | 112 | 113 | true 114 | EditTopics 115 | 116 | 117 | true 118 | EmailMass 119 | 120 | 121 | true 122 | EmailSingle 123 | 124 | 125 | true 126 | EnableNotifications 127 | 128 | 129 | true 130 | ExportReport 131 | 132 | 133 | true 134 | ImportPersonal 135 | 136 | 137 | true 138 | LightningConsoleAllowedForUser 139 | 140 | 141 | true 142 | ListEmailSend 143 | 144 | 145 | true 146 | ManageEncryptionKeys 147 | 148 | 149 | true 150 | RunReports 151 | 152 | 153 | true 154 | SelectFilesFromSalesforce 155 | 156 | 157 | true 158 | SendSitRequests 159 | 160 | 161 | true 162 | ShowCompanyNameAsUserBadge 163 | 164 | 165 | true 166 | SubmitMacrosAllowed 167 | 168 | 169 | true 170 | SubscribeToLightningReports 171 | 172 | 173 | true 174 | UseWebLink 175 | 176 | 177 | true 178 | ViewEventLogFiles 179 | 180 | 181 | true 182 | ViewHelpLink 183 | 184 | 185 | true 186 | ViewSetup 187 | 188 | 189 | -------------------------------------------------------------------------------- /pf-app/main/default/profiles/Custom%3A Marketing Profile.profile-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectForce 5 | false 6 | false 7 | 8 | 9 | FeatureAccessException 10 | false 11 | 12 | 13 | FeatureConsoleAPI 14 | false 15 | 16 | 17 | FeatureConsoleController 18 | false 19 | 20 | true 21 | 22 | false 23 | Project__c.ExpenseTracking__c 24 | false 25 | 26 | 27 | false 28 | Project__c.TotalBudget__c 29 | false 30 | 31 | 32 | BudgetLineItem__c-Budget Line Items Layout 33 | 34 | 35 | CostCenter__c-Cost Center Layout 36 | 37 | 38 | OrganizationBudget__c-Organization Budget Layout 39 | 40 | 41 | Project__c-Project Layout 42 | 43 | 44 | Feature_Console 45 | false 46 | 47 | 48 | Projects 49 | false 50 | 51 | 52 | BudgetLineItem__c 53 | DefaultOn 54 | 55 | Salesforce 56 | 57 | true 58 | AllowViewKnowledge 59 | 60 | 61 | true 62 | ApiEnabled 63 | 64 | 65 | true 66 | AssignTopics 67 | 68 | 69 | true 70 | ChatterInternalUser 71 | 72 | 73 | true 74 | ChatterInviteExternalUsers 75 | 76 | 77 | true 78 | ChatterOwnGroups 79 | 80 | 81 | true 82 | ConvertLeads 83 | 84 | 85 | true 86 | CreateCustomizeFilters 87 | 88 | 89 | true 90 | CreateCustomizeReports 91 | 92 | 93 | true 94 | CreateTopics 95 | 96 | 97 | true 98 | DistributeFromPersWksp 99 | 100 | 101 | true 102 | EditEvent 103 | 104 | 105 | true 106 | EditOppLineItemUnitPrice 107 | 108 | 109 | true 110 | EditTask 111 | 112 | 113 | true 114 | EditTopics 115 | 116 | 117 | true 118 | EmailMass 119 | 120 | 121 | true 122 | EmailSingle 123 | 124 | 125 | true 126 | EnableNotifications 127 | 128 | 129 | true 130 | ExportReport 131 | 132 | 133 | true 134 | ImportPersonal 135 | 136 | 137 | true 138 | LightningConsoleAllowedForUser 139 | 140 | 141 | true 142 | ListEmailSend 143 | 144 | 145 | true 146 | ManageEncryptionKeys 147 | 148 | 149 | true 150 | RunReports 151 | 152 | 153 | true 154 | SelectFilesFromSalesforce 155 | 156 | 157 | true 158 | SendSitRequests 159 | 160 | 161 | true 162 | ShowCompanyNameAsUserBadge 163 | 164 | 165 | true 166 | SubmitMacrosAllowed 167 | 168 | 169 | true 170 | SubscribeToLightningReports 171 | 172 | 173 | true 174 | UseWebLink 175 | 176 | 177 | true 178 | ViewEventLogFiles 179 | 180 | 181 | true 182 | ViewHelpLink 183 | 184 | 185 | true 186 | ViewSetup 187 | 188 | 189 | -------------------------------------------------------------------------------- /pf-app/main/default/classes/FeatureConsoleTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | private class FeatureConsoleTest { 3 | 4 | static testMethod void testExpenseTracking() { 5 | // reset the feature for the test execution. 6 | resetExpenseTracking(); 7 | 8 | // test that we cannot enable the expense tracking feature 9 | // before we've enabled the ExpenseTrackingPermitted boolean param. 10 | boolean caughtException = false; 11 | try { 12 | FeatureConsoleAPI.enableExpenseTracking(21367); 13 | } 14 | catch(FeatureAccessException e) { 15 | System.assert(e.getMessage().contains('not currently licensed'), 'Unexpected exception message: ' + e.getMessage()); 16 | caughtException = true; 17 | } 18 | System.assert(caughtException, 'We should not be able to enable the Expense Tracking feature.'); 19 | caughtException = false; 20 | 21 | // enable the param so we can continue our testing 22 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingPermitted',true); 23 | 24 | // test the access restriction exception handling 25 | try { 26 | FeatureConsoleAPI.expenseTrackingEnabled(23); 27 | } 28 | catch(FeatureAccessException e) { 29 | caughtException = true; 30 | } 31 | System.assert(caughtException, 'Expected an access restriction.'); 32 | caughtException = false; 33 | 34 | try { 35 | FeatureConsoleAPI.enableExpenseTracking(23); 36 | } 37 | catch(FeatureAccessException e) { 38 | caughtException = true; 39 | } 40 | System.assert(caughtException, 'Expected an access restriction.'); 41 | caughtException = false; 42 | 43 | try { 44 | FeatureConsoleAPI.disableExpenseTracking(23); 45 | } 46 | catch(FeatureAccessException e) { 47 | caughtException = true; 48 | } 49 | System.assert(caughtException, 'Expected an access restriction.'); 50 | 51 | // test that the feature is currently disabled 52 | boolean enabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 53 | System.assert(!enabled, 'Expense Tracking Feature is enabled and should not be.'); 54 | 55 | // enable the feature and then assert that it is enabled 56 | FeatureConsoleAPI.enableExpenseTracking(21367); 57 | 58 | enabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 59 | System.assert(enabled, 'Expense Tracking Feature is disabled and should not be.'); 60 | 61 | // disable the feature and then assert that it is disabled 62 | FeatureConsoleAPI.disableExpenseTracking(21367); 63 | 64 | enabled = FeatureConsoleAPI.expenseTrackingEnabled(21367); 65 | System.assert(!enabled, 'Expense Tracking Feature is enabled and should not be.'); 66 | } 67 | 68 | static testMethod void testBudgetTracking() { 69 | // reset the feature for the test execution 70 | resetBudgetTracking(); 71 | 72 | // test that we cannot enable the budget tracking feature until 73 | // we enable the BudgetTrackingPermitted boolean param. 74 | boolean caughtException = false; 75 | try { 76 | FeatureConsoleAPI.enableBudgetTracking(21367); 77 | } 78 | catch(FeatureAccessException e) { 79 | System.assert(e.getMessage().contains('not currently licensed'), 'Unexpected exception message: ' + e.getMessage()); 80 | caughtException = true; 81 | } 82 | System.assert(caughtException, 'We should not be able to enable the Budget Tracking feature.'); 83 | caughtException = false; 84 | // enable the param so we can continue our testing 85 | FeatureManagement.setPackageBooleanValue('BudgetTrackingPermitted',true); 86 | 87 | // test the access restriction exception handling 88 | try { 89 | FeatureConsoleAPI.budgetTrackingEnabled(23); 90 | } 91 | catch(FeatureAccessException e) { 92 | caughtException = true; 93 | } 94 | System.assert(caughtException, 'Expected an access restriction.'); 95 | caughtException = false; 96 | 97 | try { 98 | FeatureConsoleAPI.enableBudgetTracking(23); 99 | } 100 | catch(FeatureAccessException e) { 101 | caughtException = true; 102 | } 103 | System.assert(caughtException, 'Expected an access restriction.'); 104 | caughtException = false; 105 | 106 | try { 107 | FeatureConsoleAPI.disableBudgetTracking(23); 108 | } 109 | catch(FeatureAccessException e) { 110 | caughtException = true; 111 | } 112 | System.assert(caughtException, 'Expected an access restriction.'); 113 | 114 | // assert that the feature is currently disabled 115 | boolean enabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 116 | System.assert(!enabled, 'Budget Tracking Feature is enabled and should not be.'); 117 | 118 | // enable the feature and assert that it is now enabled 119 | FeatureConsoleAPI.enableBudgetTracking(21367); 120 | 121 | enabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 122 | System.assert(enabled, 'Budget Tracking Feature is disabled and should not be.'); 123 | 124 | // disable the feature and assert that it is disabled 125 | FeatureConsoleAPI.disableBudgetTracking(21367); 126 | 127 | enabled = FeatureConsoleAPI.budgetTrackingEnabled(21367); 128 | System.assert(!enabled, 'Budget Tracking Feature is enabled and should not be.'); 129 | } 130 | 131 | static testMethod void testProjectCount() { 132 | // create a probject so we can ensure we're getting test coverage 133 | // of our project count trigger 134 | Project__c project = new Project__c(name='myProject23'); 135 | project.TotalBudget__c = 37; 136 | 137 | insert project; 138 | } 139 | 140 | private static void resetExpenseTracking() { 141 | // turn off the gating feature param just in case it's turned on 142 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingPermitted', false); 143 | // disable the feature just in case it's enabled 144 | FeatureConsoleAPI.disableExpenseTracking(21367); 145 | } 146 | 147 | private static void resetBudgetTracking() { 148 | // turn off the gating feature param just in case it's turned on 149 | FeatureManagement.setPackageBooleanValue('BudgetTrackingPermitted', false); 150 | // disable the feature just in case it's enabledd 151 | FeatureConsoleAPI.disableBudgetTracking(21367); 152 | } 153 | } -------------------------------------------------------------------------------- /pf-app/main/default/profiles/Custom%3A Support Profile.profile-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectForce 5 | false 6 | false 7 | 8 | 9 | FeatureAccessException 10 | false 11 | 12 | 13 | FeatureConsoleAPI 14 | false 15 | 16 | 17 | FeatureConsoleController 18 | false 19 | 20 | true 21 | 22 | false 23 | Project__c.ExpenseTracking__c 24 | false 25 | 26 | 27 | false 28 | Project__c.TotalBudget__c 29 | false 30 | 31 | 32 | BudgetLineItem__c-Budget Line Items Layout 33 | 34 | 35 | CostCenter__c-Cost Center Layout 36 | 37 | 38 | OrganizationBudget__c-Organization Budget Layout 39 | 40 | 41 | Project__c-Project Layout 42 | 43 | 44 | Feature_Console 45 | false 46 | 47 | 48 | Projects 49 | false 50 | 51 | 52 | BudgetLineItem__c 53 | DefaultOn 54 | 55 | Salesforce 56 | 57 | true 58 | AllowViewKnowledge 59 | 60 | 61 | true 62 | ApiEnabled 63 | 64 | 65 | true 66 | AssignTopics 67 | 68 | 69 | true 70 | ChatterInternalUser 71 | 72 | 73 | true 74 | ChatterInviteExternalUsers 75 | 76 | 77 | true 78 | ChatterOwnGroups 79 | 80 | 81 | true 82 | ConvertLeads 83 | 84 | 85 | true 86 | CreateCustomizeFilters 87 | 88 | 89 | true 90 | CreateCustomizeReports 91 | 92 | 93 | true 94 | CreateTopics 95 | 96 | 97 | true 98 | DistributeFromPersWksp 99 | 100 | 101 | true 102 | EditEvent 103 | 104 | 105 | true 106 | EditOppLineItemUnitPrice 107 | 108 | 109 | true 110 | EditTask 111 | 112 | 113 | true 114 | EditTopics 115 | 116 | 117 | true 118 | EmailMass 119 | 120 | 121 | true 122 | EmailSingle 123 | 124 | 125 | true 126 | EnableNotifications 127 | 128 | 129 | true 130 | ExportReport 131 | 132 | 133 | true 134 | ImportPersonal 135 | 136 | 137 | true 138 | LightningConsoleAllowedForUser 139 | 140 | 141 | true 142 | ListEmailSend 143 | 144 | 145 | true 146 | ManageCases 147 | 148 | 149 | true 150 | ManageEncryptionKeys 151 | 152 | 153 | true 154 | ManageSolutions 155 | 156 | 157 | true 158 | RunReports 159 | 160 | 161 | true 162 | SelectFilesFromSalesforce 163 | 164 | 165 | true 166 | SendSitRequests 167 | 168 | 169 | true 170 | ShowCompanyNameAsUserBadge 171 | 172 | 173 | true 174 | SubmitMacrosAllowed 175 | 176 | 177 | true 178 | SubscribeToLightningReports 179 | 180 | 181 | true 182 | TransferAnyCase 183 | 184 | 185 | true 186 | UseWebLink 187 | 188 | 189 | true 190 | ViewEventLogFiles 191 | 192 | 193 | true 194 | ViewHelpLink 195 | 196 | 197 | true 198 | ViewSetup 199 | 200 | 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Force App 2 | 3 | This app is a reference implementation to showcase the capabilities of the Feature Management product offering. The app employs a basic feature console through which subscribers can enable or disable features. These features have been enabled by a License Management Organization (LMO) that has installed the Feature Management App (FMA). There are various UI features that are visible only when these features are enabled in the subscriber organization. There are also a couple of custom objects that are protected, one of which is de-protected when the Budget Tracking feature is enabled. 4 | 5 | We realize that you might not be able to or want to make a new Managed - Released version of this package and associate it with an LMO. So, for the purposes of this repository, we’ve tried to put together a workflow that showcases most of the features of Feature Management using a scratch org, with some caveats. The first of these caveats is that, because the package isn’t in a Managed - Installed state, things like protected custom objects and protected custom permissions don’t behave the same way they would if this package were Managed - Released and installed in a subscriber org. We can, however, showcase the enabling and disabling of features for subscribers and show how to write automated tests against this type of behavior. 6 | 7 | ## Dev, Build, and Test 8 | 9 | This app and repository are designed to be used with the source-driven development flow introduced with Salesforce DX. Here's how to get started with this app and repository. 10 | 11 | First, clone this repository: 12 | 13 | ``` 14 | git clone https://github.com/forcedotcom/project-force 15 | ``` 16 | 17 | Then, create your own branch: 18 | 19 | ``` 20 | cd ProjectForce 21 | git checkout -b myBranch 22 | ``` 23 | 24 | After you create your branch, you can work in that branch and make changes as you see fit. To start, you might want to edit the JSON config files within the project structure to reflect your personal preferences. 25 | 26 | From this point on, we’re going to assume you are using the Salesforce CLI, that you have created a Dev Hub, and that you’ve authorized the Salesforce CLI to use that Dev Hub. If you haven't completed those tasks, check out the _[Salesforce DX Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup)_. 27 | 28 | Create a scratch org and set that org as the default org for further SFDX commands: 29 | 30 | ``` 31 | sfdx force:org:create --setdefaultusername --setalias test --definitionfile config/project-scratch-def.json 32 | ``` 33 | 34 | Push all of the Project Force metadata into your newly created scratch org: 35 | 36 | ``` 37 | sfdx force:source:push 38 | ``` 39 | 40 | Next, open your scratch org in the browser 41 | 42 | ``` 43 | sfdx force:org:open 44 | ``` 45 | 46 | For your scratch org user’s profile, ensure the following selections are active: 47 | 48 | ![image](https://user-images.githubusercontent.com/45772/30082726-1464101c-9249-11e7-9cfb-d34e5889dccb.png) 49 | 50 | Now, select the Project Force app the app menu (if you’re using Lightning Experience) or from the app dropdown (in Salesforce Classic). 51 | 52 | Then, click the Projects tab, and create a new `Project__c` object there. Click the Projects tab again to see the list view. Notice that there are only two columns for the record ID and the record name. 53 | 54 | ![image](https://user-images.githubusercontent.com/45772/32290491-de179194-beff-11e7-8d77-567793c68e1a.png) 55 | 56 | Next, click the Feature Console tab. Select the **Expense Tracking** feature, and click **Save**. An error message on the page says, “Expense Tracking feature not currently licensed.” 57 | 58 | ![image](https://user-images.githubusercontent.com/31550188/30071402-c6374a46-9223-11e7-931e-6ad24d2b6745.png) 59 | 60 | You’re seeing this error because the `ExpenseTrackingPermitted` feature parameter is currently set to false. This feature parameter determines whether or not this feature can be enabled in a subscriber org. Typically, this value would be changed via an LMO with the FMA installed. For our purposes, working with a scratch org, we’ll change the value in a different way to simulate this process. 61 | 62 | Edit the `ExpenseTrackingPermitted.featureParameterBoolean-meta.xml` file in the `pf-app/main/default/featureParameters` directory and set the feature parameter’s value to true: 63 | 64 | ``` 65 | 66 | 67 | LmoToSubscriber 68 | Expense Tracking Permitted 69 | true 70 | 71 | ``` 72 | 73 | Save the file, and then push the source into the scratch org: 74 | 75 | ``` 76 | sfdx force:source:push 77 | ``` 78 | 79 | Back in your scratch org, return to the Feature Console tab. Try again to enable the **Expense Tracking** feature, and then click **Save**. This should now be successful. 80 | 81 | ![image](https://user-images.githubusercontent.com/31550188/30071529-42e21eae-9224-11e7-9d87-d6b5b4e1e131.png) 82 | 83 | Click the Projects tab, and notice there is now a new column for Expense Tracking in the list view. 84 | 85 | ![image](https://user-images.githubusercontent.com/45772/32290490-de0011f4-beff-11e7-8621-752fa5532d85.png) 86 | 87 | If you disable the Expense Tracking feature in the Feature Console, this column will again be hidden. You can also repeat this process for the Budget Tracking feature, which is gated by the `BudgetTrackingPermitted` feature parameter. 88 | 89 | You can’t call `FeatureManagement` Apex methods to edit LMO-to-Subscriber feature parameters. Prior to the Winter ’18 release, it wasn’t possible to edit LMO-to-Subscriber feature parameters in an Apex test either. We’ve now allowed this for Apex test executions in the same namespace as the feature parameter that is being edited. This way, you can test your features by setting different values for your feature parameters in an Apex test, where the values aren’t persisted in the database at the end of your test run. Default values remain unchanged, and you can test things like boolean parameters that allow access to features or UI components, integer parameters that set limits, or date parameters that serve as expiration dates. 90 | 91 | A sample test class that does this type of testing is `pf-app/main/default/classes/FeatureConsoleTest.cls`. Lines like this one help us test the rest of the feature after we’ve verified that the feature can’t be enabled without setting certain values: 92 | 93 | ``` 94 | // enable the param so we can continue our testing 95 | FeatureManagement.setPackageBooleanValue('ExpenseTrackingPermitted',true); 96 | ``` 97 | 98 | To run `FeatureConsoleTest`, try this: 99 | 100 | ``` 101 | sfdx force:apex:test:run -n FeatureConsoleTest -r human 102 | ``` 103 | 104 | This command should produce results that look like this: 105 | 106 | ![image](https://user-images.githubusercontent.com/31550188/30071140-f456df28-9222-11e7-8c6a-9e93af46492c.png) 107 | 108 | From here, to test things like protected custom objects and protected custom permissions, you can take the next steps and deploy this metadata into a release org, upload a version of a package containing this metadata, and install it in another scratch org for testing. That is about as close as you can get to the full Feature Management experience without using an LMO to enable feature parameters. 109 | 110 | To get the best experience using this source, you’ll want to upload a package version and associate it with an LMO. This way you can leverage the functionality of enabling features for a subscriber org from the LMO. To enable the Budget Tracking feature, edit the `BudgetTrackingPermitted` boolean feature parameter in your LMO and set its value to `true`. In your subscriber org, you’ll then be able to go to the Feature Console tab and enable the feature there. Enabling the feature in the subscriber org does a couple things. It sets the `BudgetTrackingEnabled` feature parameter to `true` so you can see the Total Budget column on the Projects tab (assuming you’ve created some Project records), and it also removes the protection on the Budget Line Item custom object—and by association its custom tab. It’s also important to note that the `BudgetTrackingEnabled` parameter serves two purposes. First, it controls the visibility of a column on the Projects page. It also reports back to the LMO to inform the LMO that this subscriber has enabled the Budget Tracking feature. The Expense Tracking feature behaves almost identically to the Budget Tracking feature, except it has no custom objects associated with it. It’s important to note that in order for you to see the Budget Line Items tab after enabling the feature in your subscriber org, you’ll need to re-select the app from the app menu (in Lightning Experience) or navigate to one of the other tabs (in Salesforce Classic). Either one of these actions re-renders the tabs in the app for you. 111 | 112 | On the subject of feature parameters that deliver metrics data back to the LMO from the subscriber: there are a few of these feature parameters baked into this app. We discussed the `BudgetTrackingEnabled` metric above. There is also a similar parameter for the expense tracking feature, called `ExpenseTrackingEnabled`. Lastly, there is an integer parameter called `CurrentProjectCount`, which is intended to keep track of how many Project objects have been created in the subscriber org. The product is designed such that these metrics’ values are delivered to the LMO once per day. 113 | 114 | There are also a few things we’ve purposely left undone. First, there is a custom object with a custom tab called Organization Budget. This object is protected, and there is no mechanism built into the app with which to remove its protection. After reviewing the source of the project, you can practice by adding another feature that uses this object. Be sure to add new feature parameters, associated Apex to manipulate your feature parameters and remove the protection on the object, and an item on the Feature Console tab so the feature can be enabled in the subscriber org. There are also some date and integer feature parameters that aren’t currently in use. You can try to incorporate these feature parameters into the app to work with the various data types available to you. 115 | 116 | 117 | ## Resources 118 | 119 | _ISVforce Guide_: [Manage Features](https://developer.salesforce.com/docs/atlas.en-us.packagingGuide.meta/packagingGuide/fma_manage_features.htm) 120 | 121 | _[Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev)_ 122 | 123 | [Salesforce DX Dreamhouse App](https://github.com/DreamhouseApp/dreamhouse-sfdx) 124 | 125 | ## Description of Files and Directories 126 | 127 | `pf-app`: 128 | This is the directory where all of the metadata for the app is contained. It is currently in the Salesforce DX source format, and would need to be converted using `sfdx force:source:convert` before it can be deployed to a release org. 129 | 130 | `config`: 131 | This is the directory where the scratch org configuration JSON file, `project-scratch-def.json`, lives. Edit this file to match your personal preferences. 132 | 133 | `sfdx-project.json`: 134 | This is the main project configuration file, in JSON format. Edit this file to match your personal preferences. 135 | 136 | 137 | -------------------------------------------------------------------------------- /pf-app/main/default/profiles/Admin.profile-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectForce 5 | false 6 | true 7 | 8 | 9 | FeatureAccessException 10 | true 11 | 12 | 13 | FeatureConsoleAPI 14 | true 15 | 16 | 17 | FeatureConsoleController 18 | true 19 | 20 | 21 | FeatureConsoleTest 22 | true 23 | 24 | 25 | ProjectExtension 26 | true 27 | 28 | false 29 | 30 | true 31 | Project__c.ExpenseTracking__c 32 | true 33 | 34 | 35 | true 36 | Project__c.TotalBudget__c 37 | true 38 | 39 | 40 | BudgetLineItem__c-Budget Line Items Layout 41 | 42 | 43 | CostCenter__c-Cost Center Layout 44 | 45 | 46 | OrganizationBudget__c-Organization Budget Layout 47 | 48 | 49 | Project__c-Project Layout 50 | 51 | 52 | true 53 | true 54 | true 55 | true 56 | true 57 | BudgetLineItem__c 58 | true 59 | 60 | 61 | true 62 | true 63 | true 64 | true 65 | true 66 | CostCenter__c 67 | true 68 | 69 | 70 | true 71 | true 72 | true 73 | true 74 | true 75 | OrganizationBudget__c 76 | true 77 | 78 | 79 | true 80 | true 81 | true 82 | true 83 | true 84 | Project__c 85 | true 86 | 87 | 88 | Feature_Console 89 | true 90 | 91 | 92 | Projects 93 | true 94 | 95 | Salesforce 96 | 97 | true 98 | ActivateContract 99 | 100 | 101 | true 102 | ActivateOrder 103 | 104 | 105 | true 106 | AddDirectMessageMembers 107 | 108 | 109 | true 110 | AllowUniversalSearch 111 | 112 | 113 | true 114 | AllowViewKnowledge 115 | 116 | 117 | true 118 | ApiEnabled 119 | 120 | 121 | true 122 | AssignPermissionSets 123 | 124 | 125 | true 126 | AssignTopics 127 | 128 | 129 | true 130 | AuthorApex 131 | 132 | 133 | true 134 | BulkMacrosAllowed 135 | 136 | 137 | true 138 | CanInsertFeedSystemFields 139 | 140 | 141 | true 142 | CanUseNewDashboardBuilder 143 | 144 | 145 | true 146 | CanVerifyComment 147 | 148 | 149 | true 150 | ChatterEditOwnPost 151 | 152 | 153 | true 154 | ChatterEditOwnRecordPost 155 | 156 | 157 | true 158 | ChatterFileLink 159 | 160 | 161 | true 162 | ChatterInternalUser 163 | 164 | 165 | true 166 | ChatterInviteExternalUsers 167 | 168 | 169 | true 170 | ChatterOwnGroups 171 | 172 | 173 | true 174 | ConnectOrgToEnvironmentHub 175 | 176 | 177 | true 178 | ContentAdministrator 179 | 180 | 181 | true 182 | ContentWorkspaces 183 | 184 | 185 | true 186 | ConvertLeads 187 | 188 | 189 | true 190 | CreateCustomizeDashboards 191 | 192 | 193 | true 194 | CreateCustomizeFilters 195 | 196 | 197 | true 198 | CreateCustomizeReports 199 | 200 | 201 | true 202 | CreateDashboardFolders 203 | 204 | 205 | true 206 | CreateReportFolders 207 | 208 | 209 | true 210 | CreateTopics 211 | 212 | 213 | true 214 | CreateWorkBadgeDefinition 215 | 216 | 217 | true 218 | CreateWorkspaces 219 | 220 | 221 | true 222 | CustomizeApplication 223 | 224 | 225 | true 226 | DelegatedTwoFactor 227 | 228 | 229 | true 230 | DeleteActivatedContract 231 | 232 | 233 | true 234 | DeleteTopics 235 | 236 | 237 | true 238 | DistributeFromPersWksp 239 | 240 | 241 | true 242 | EditActivatedOrders 243 | 244 | 245 | true 246 | EditBillingInfo 247 | 248 | 249 | true 250 | EditBrandTemplates 251 | 252 | 253 | true 254 | EditCaseComments 255 | 256 | 257 | true 258 | EditEvent 259 | 260 | 261 | true 262 | EditHtmlTemplates 263 | 264 | 265 | true 266 | EditKnowledge 267 | 268 | 269 | true 270 | EditMyDashboards 271 | 272 | 273 | true 274 | EditMyReports 275 | 276 | 277 | true 278 | EditOppLineItemUnitPrice 279 | 280 | 281 | true 282 | EditPublicDocuments 283 | 284 | 285 | true 286 | EditPublicFilters 287 | 288 | 289 | true 290 | EditPublicTemplates 291 | 292 | 293 | true 294 | EditReadonlyFields 295 | 296 | 297 | true 298 | EditTask 299 | 300 | 301 | true 302 | EditTopics 303 | 304 | 305 | true 306 | EmailMass 307 | 308 | 309 | true 310 | EmailSingle 311 | 312 | 313 | true 314 | EnableCommunityAppLauncher 315 | 316 | 317 | true 318 | EnableNotifications 319 | 320 | 321 | true 322 | ExportReport 323 | 324 | 325 | true 326 | FieldServiceAccess 327 | 328 | 329 | true 330 | ImportCustomObjects 331 | 332 | 333 | true 334 | ImportLeads 335 | 336 | 337 | true 338 | ImportPersonal 339 | 340 | 341 | true 342 | InstallPackaging 343 | 344 | 345 | true 346 | LightningConsoleAllowedForUser 347 | 348 | 349 | true 350 | LightningExperienceUser 351 | 352 | 353 | true 354 | ListEmailSend 355 | 356 | 357 | true 358 | ManageAnalyticSnapshots 359 | 360 | 361 | true 362 | ManageAuthProviders 363 | 364 | 365 | true 366 | ManageBusinessHourHolidays 367 | 368 | 369 | true 370 | ManageCallCenters 371 | 372 | 373 | true 374 | ManageCases 375 | 376 | 377 | true 378 | ManageCategories 379 | 380 | 381 | true 382 | ManageCertificates 383 | 384 | 385 | true 386 | ManageContentPermissions 387 | 388 | 389 | true 390 | ManageContentProperties 391 | 392 | 393 | true 394 | ManageContentTypes 395 | 396 | 397 | true 398 | ManageCustomPermissions 399 | 400 | 401 | true 402 | ManageCustomReportTypes 403 | 404 | 405 | true 406 | ManageDashbdsInPubFolders 407 | 408 | 409 | true 410 | ManageDataCategories 411 | 412 | 413 | true 414 | ManageDataIntegrations 415 | 416 | 417 | true 418 | ManageDynamicDashboards 419 | 420 | 421 | true 422 | ManageEmailClientConfig 423 | 424 | 425 | true 426 | ManageExchangeConfig 427 | 428 | 429 | true 430 | ManageHealthCheck 431 | 432 | 433 | true 434 | ManageInteraction 435 | 436 | 437 | true 438 | ManageInternalUsers 439 | 440 | 441 | true 442 | ManageIpAddresses 443 | 444 | 445 | true 446 | ManageKnowledge 447 | 448 | 449 | true 450 | ManageKnowledgeImportExport 451 | 452 | 453 | true 454 | ManageLeads 455 | 456 | 457 | true 458 | ManageLoginAccessPolicies 459 | 460 | 461 | true 462 | ManageMobile 463 | 464 | 465 | true 466 | ManageNetworks 467 | 468 | 469 | true 470 | ManagePackageLicenses 471 | 472 | 473 | true 474 | ManagePasswordPolicies 475 | 476 | 477 | true 478 | ManageProfilesPermissionsets 479 | 480 | 481 | true 482 | ManagePvtRptsAndDashbds 483 | 484 | 485 | true 486 | ManageRemoteAccess 487 | 488 | 489 | true 490 | ManageReportsInPubFolders 491 | 492 | 493 | true 494 | ManageRoles 495 | 496 | 497 | true 498 | ManageSearchPromotionRules 499 | 500 | 501 | true 502 | ManageSharing 503 | 504 | 505 | true 506 | ManageSolutions 507 | 508 | 509 | true 510 | ManageSynonyms 511 | 512 | 513 | true 514 | ManageUnlistedGroups 515 | 516 | 517 | true 518 | ManageUsers 519 | 520 | 521 | true 522 | MassInlineEdit 523 | 524 | 525 | true 526 | MergeTopics 527 | 528 | 529 | true 530 | ModerateChatter 531 | 532 | 533 | true 534 | ModifyAllData 535 | 536 | 537 | true 538 | NewReportBuilder 539 | 540 | 541 | true 542 | Packaging2 543 | 544 | 545 | true 546 | RemoveDirectMessageMembers 547 | 548 | 549 | true 550 | ResetPasswords 551 | 552 | 553 | true 554 | RunReports 555 | 556 | 557 | true 558 | ScheduleReports 559 | 560 | 561 | true 562 | SelectFilesFromSalesforce 563 | 564 | 565 | true 566 | SendExternalEmailAvailable 567 | 568 | 569 | true 570 | SendSitRequests 571 | 572 | 573 | true 574 | ShareInternalArticles 575 | 576 | 577 | true 578 | ShowCompanyNameAsUserBadge 579 | 580 | 581 | true 582 | SolutionImport 583 | 584 | 585 | true 586 | SubmitMacrosAllowed 587 | 588 | 589 | true 590 | SubscribeReportToOtherUsers 591 | 592 | 593 | true 594 | SubscribeReportsRunAsUser 595 | 596 | 597 | true 598 | SubscribeToLightningReports 599 | 600 | 601 | true 602 | TransferAnyCase 603 | 604 | 605 | true 606 | TransferAnyEntity 607 | 608 | 609 | true 610 | TransferAnyLead 611 | 612 | 613 | true 614 | UseTeamReassignWizards 615 | 616 | 617 | true 618 | UseWebLink 619 | 620 | 621 | true 622 | ViewAllData 623 | 624 | 625 | true 626 | ViewAllUsers 627 | 628 | 629 | true 630 | ViewDataAssessment 631 | 632 | 633 | true 634 | ViewDataCategories 635 | 636 | 637 | true 638 | ViewEventLogFiles 639 | 640 | 641 | true 642 | ViewHealthCheck 643 | 644 | 645 | true 646 | ViewHelpLink 647 | 648 | 649 | true 650 | ViewMyTeamsDashboards 651 | 652 | 653 | true 654 | ViewPublicDashboards 655 | 656 | 657 | true 658 | ViewPublicReports 659 | 660 | 661 | true 662 | ViewSetup 663 | 664 | 665 | true 666 | WorkCalibrationUser 667 | 668 | 669 | --------------------------------------------------------------------------------