├── .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 | 
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 | 
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 | 
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 | 
82 |
83 | Click the Projects tab, and notice there is now a new column for Expense Tracking in the list view.
84 |
85 | 
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 | 
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 |
58 | true
59 |
60 |
61 | true
62 | true
63 | true
64 | true
65 | true
66 |
67 | true
68 |
69 |
70 | true
71 | true
72 | true
73 | true
74 | true
75 |
76 | true
77 |
78 |
79 | true
80 | true
81 | true
82 | true
83 | true
84 |
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 |
--------------------------------------------------------------------------------