Customisation
When to customise?
When considering customisation, think about the business benefit of a customisation versus the cost.

| Benefit | Cost |
|---|---|
| Adapt to Client’s business processes | Blocks future updates to that area |
| More direct business value | Additional maintenance cost |
| Enhanced end-user experiences | Additional complexity / update risk |
Some customisations have higher costs than others
| Example | Cost of ownership |
|---|---|
| Reporting Customisation | Low |
| Additional form fields | Low |
| New entities/data structure changes | Medium |
| Changes to OOTB JavaScript / DOM | High |
How to customise?
One of the key reasons to choose Altus and the Power Platform is to facilitate customisation of the product to suit your requirements. With Altus you can customise it yourself or engage us to help you.
Do it yourself
GOLDEN RULES FOR CUSTOMISATION
- Multiple environments are required for Application Lifecycle Management (ALM)
- Production environments should not include unmanaged layers. Unmanaged layers in Production can lead to several problems:
- Unpredictable Behaviour: Unmanaged layers can override managed solutions, leading to unexpected behaviour and unexpected bugs.
- Difficult to Track: Changes made directly in Production are harder to track and audit.
- Maintenance Challenges: It complicates the process of updates and maintenance, as unmanaged customisations need to be manually reconciled with new versions of managed solutions
- Microsoft includes a setting to enforce this.
- Each Development environment used for solution layer creation should only host one unmanaged layer.
- When exporting a layer into a managed layer, predictable behaviour can only be assured if there is one unmanaged layer that encapsulates all the changes and dependencies of those changes.

Power Apps is an open and accessible platform for citizen developers and experienced developers to perform customisations themselves. We advise that all customer customisations are performed in a Development (DEV) environment in a dedicated solution layer and then deployed to the Production (PROD) environment as a managed layer. This allows good separation and change control processes to take place.

Engage a Consulting Partner

To provide the highest level of expertise and support, Altus partners can provide detailed customisation services for Altus and Client Care to ensure that your deployment remains supported and up to date.
It is recommended that Consulting led customisations to Altus are stored in a layer above the base product and in the case where there are multiple environments (DEV, TEST, PROD) that unmanaged layers are used DEV and TEST are deployed into subsequent environments as Managed. Customer maintained layers can also be present to provide a quicker way to make changes without having to engage a partner every time.

Warning
Each environment must only contain one unmanaged layer used for development.
Altus customisation
The Altus solution as a set of layers over the existing project and Power Platform components. The additional components that are provided as part of the solution have the Customizable flag enabled (where possible) to allow almost any provided Dynamics component to be tailored to meet the business requirements. Where the item is not directly customisable (Security Roles, PCF Components, and the Teams App) we have taken steps to ensure that these have configuration options available to control their behaviour.
The solution layering concept is a common technique used throughout the Power Platform, and reviewing this general introduction to Solutions from Microsoft is recommended before getting started, with a more advanced discussion of best practices for Solution Lifecycle Management available here.
In the case of Altus, the solution layering can be visualised as:

The Solution Layers are (from the bottom up):
- (PFTW Edition Only) Microsoft Project Roadmap. Roadmap functionality is part of the Project Plan 3 and Project Plan 5 licenses. The Roadmap solution provides base level functionality for many project related functions and was historically released first.
- (PFTW Edition Only) Project for the Web. This layer provided by Microsoft contains the back-end Dynamics components required for the Project for the Web functionality.
- Kaizen. This layer contains the Altus Tables and functions that are not specific to Project for the Web, and common to all Altus solutions.
- Atsumeru. Specific functionality for the Altus execution tool independent solution.
- (PFTW Edition Only) IQ PFTW. This layer includes Tables, relationships and functions that provide integration with Microsoft Project for the Web.
- Customisations. This layer can be added by the customer, or by a Partner on the Customer's behalf. It is either the "Default Solution" or a solution that is managed in a customer owned DEVOPS process.
Altus applies solutions in layers to provide functionality to the end user and customer specific customisations can then be applied on top to cater for specific customer needs.
Limitations
Important
Some elements in the Dataverse do not seem to handle Solution Layering. Please see below a list of the elements we have encountered which we recommend not to modify from our Altus Solutions.
SenseiProject Javascript bundle
It is not supported to customize the 'SenseiProject.bundle.js' JavaScript bundle in Webresources within your solution. Creating a customization layer will prevent future updates to this file from being visible in your environment.
Security Roles
Because Security Roles do not seem handle solution layering (and any changes made to them would be overwritten on the next update), all of the security roles shipped with Altus are shipped as non-customisable. If you require custom security roles, you can add new security roles in your environment.
Control Configuration at the Table level
Dataverse provides a capability (via the Classic interface) to modify the default Control used for each Table. This configuration change does not handle Solution Layering and any change made to this setting will either be deleted automatically whenever the base Solution updates, or will cause the base Solution update to fail completely if the control configuration is housed in another managed Solution layer.
Altus strictly recommend not modifying the Control settings at the Table level for any of the Tables that are shipped with Altus.
(Note: Modifying the control for a subgrid at the Form level does not have this limitation as the layering is handled correctly by the Form).
More details here
Security Role Query
Partners have access to typescript/javascript code to query the current users security roles by ID together with a list of constants that represent all the available out-of-the-box security roles.
This query will check to see if users have a given security role, and can be used if Security Roles Display Names are changing.
Query:
// Altus Roles
export const ExecutiveRoleId = "dfcfe799-f413-eb11-a813-000d3ae11abd";
export const AdminRoleId = "4c0f0c74-8b09-ea11-a811-000d3a530fe5";
export const ProjectRoleId = "9959280b-3621-ea11-a810-000d3a530fe5";
export const ProgramManagerRoleId = "f9dfbebf-fb13-eb11-a813-000d3ae11abd";
export const ProgramManagerTeamRoleId = "35e21d64-fc13-eb11-a813-000d3ae11abd";
export const PortfolioRoleId = "0bec81ef-9b09-ea11-a811-000d3a530fe5";
export const PortfolioManagerRoleId = "6032594f-fa13-eb11-a813-000d3ae11abd";
export const PortfolioManagerTeamRoleId = "ec929215-fb13-eb11-a813-000d3ae11abd";
export const ProposalManagerRoleId = "6734a4a6-fc13-eb11-a813-000d3ae11abd";
export const IdeaUserRoleId = "b0a82810-f813-eb11-a813-000d3ae11abd";
export const ChallengUserRoleId = "73ce88a4-f713-eb11-a813-000d3ae11abd";
export const StrategyRoleId = "657aa149-9c09-ea11-a811-000d3a530fe5";
export const StrategyExecutiveRoleId = "a180425f-fd13-eb11-a813-000d3ae11abd";
export const PMOUserRoleId = "92010470-f913-eb11-a813-000d3ae11abd";
export const ResourceManagerRoleId = "e53a8843-0bcf-eb11-bacc-0022481520cd";
export function currentUserHasRole(roleID: string): Xrm.Async.PromiseLike<boolean> {
let roles = Xrm.Utility.getGlobalContext().userSettings.roles.get();
return Xrm.WebApi.retrieveMultipleRecords(
"role",
`?$select=name,roleid&$filter=_parentrootroleid_value eq '${roleID}'`)
.then(
(result) => {
if (result.entities.length > 0) {
for (let item of result.entities) {
if (!(roles.find((x) => x.id == item.roleid) === undefined)) {
return true;
}
}
return false;
} else {
throw new Error(`No Security Roles Found!`);
}
},
(error) => {
throw new Error(`Error - ${error.message}`);
}
)
.catch((error) => {
console.log(error);
return false;
});
}
usage
currentUserHasRole(StrategyExecutiveRoleId)
.then(result => {
console.log(result);
});
or
const result = await currentUserHasRole(StrategyExecutiveRoleId)
console.log(result);
SharePoint group sites
It is a common requirement to want to add elements or customise the SharePoint Site that is created in conjunction to the Microsoft (M365) Group. The approach to facilitating this is Using Site Designs to Manage Project Life Cycles.
Using techniques such as Hubs Sites, Site Designs, Site Scripts and connected Power Automate Flows allow for an almost infinite level of site customisation capabilities.
Customising Altus
Any change to Altus needs to be done via a solution, and to do this requires a Publisher.
Before you make any changes to Altus you will need to decide how you are going to make this change and how this change will interact with any base updates made to Altus. If you are making a considerable change to a form within Altus in line with a Client's request, and they don't want it to be automatically updated by any Altus base changes, it is probably best to make a copy of the form and work from this. This will mean that no changes will automatically be made to the form with future Altus releases. Any changes will need to be manually updated.
Note: Some items within Altus, such as PCFs will always get updates.
Adding a Publisher
We suggest if you are going to make customisations to your environment that in your development environment you configure a custom publisher which is not prefixed with "sensei" to avoid collisions with the Altus solution.
- In order to create your publisher navigate to make.powerapps.com then select your Environment from the top right corner.
Now we want to create our new Publisher inside the environment.
- Click Solutions from the left-hand side bar
- Select the Publishers tab
- Click + New publisher from the main Home bar
- Type the Display Name for your publisher, i.e. "Dynamics Edge"
- Type the Name for your publisher, this value shouldn't have any spaces. The Name will mirror the Display Name without spaces by default
- Type a Prefix for your publisher, as shown by marker 7 the prefix appears at start of any entities or objects created using the publisher. In this example it's "dynedge"
- This is a preview of what a new Object name would look like i.e. dynedge_Object
- Click Save and the publisher will be created if there is no collision with an existing name in the system.
Adding a Unmanaged Solution
We want to create a new unmanaged solution using our new Publisher
- Click Solutions from the left-hand side bar
- Select the Solutions tab
- Click + New solution from the main Home bar
- Type the Display Name for your solution
- Type the Name for your customisation, this value shouldn't have any spaces. The Name will mirror the Display Name without spaces or illegal characters by default
- Select the Publisher you want to use: "Dynamics Edge" in our example
- Unmanaged refers to the type of solution being generated
- Click Create to create the solution.
Adding a column
- In order to create your publisher navigate to make.powerapps.com then select your Environment from the top right corner.
Now we need to edit our unmanaged solution. Adding in our case the existing entity and associated form.
- Click Solutions from the left-hand side bar
- Find your unmanaged customization layer i.e. "Customization" and select the ellipsis.
- Click Edit.
To expand on an existing entity:
- Select in the Top ribbon select Add Existing
- Select Table (also known as Entity)
In our example we are going to add "sensei_risk" table to our solution.
- Search for "sensei_risk" in the top right search box
- Confirm that the Name also known as internal name matches
- Select the checkbox
- Click Next to proceed to add to unmanaged solution
To add existing objects to our customisation solution:
- Select Select objects
Adding the Main Information form to a customised solution
- Select Forms
- Notice the Information form definition under display name
- In the Form type column it should say that it is a "Main" form
- In the Managed externally? it should say "Managed", this implies this will create a solution layer which can prevent further updates to this entity. So it's recommended to keep back-ups of customisations in case you even need to restore them
- Select the Columns checkbox
- Click Add to add it to the solution.
- This shows our form has been added to the customisation layer
- Add the selected form to the solution layer.
Now we can observe that our customisation has 1 entity "Risk" added to it.
To add a Column to it
- Click the ellipsis
- Click Edit
Adding a Simple Column
- Click the New Column button in the ribbon
This will open the side pane which will allow for entering of the following details
Display Name
- This is the value that will be displayed as the Column name when added to a form
Description
- Optional: the description of the column being created
Data type
- The column(field) data type needs to be one of the following Microsoft Documentation
Format
- Then format for this particular data type
Behavior
- The options here are Simple or Calculated (Rollup is an option for other data types). Calculated/Rollup will require saving the new element in order to modify the calculated column(field)
Required
- Defining if the value is optional/required/recommended
In the following we look at "Advanced options" (2) dropdown
- Type a Display name in our example we have used "Custom Risk"
- Expand Advanced options
- Take note that the Schema name starts with "dynedge_" which is from the publisher for the solution and CustomRisk is the Display name removing invalid characters. Making the final "Schema name" equal "dynedge_CustomRisk". This can be changed at this point, but note that the "Schema name" cannot be changed once it has been created.
- Enter the Maximum characters count for the column
- Save the current configuration for your new column.
After a few seconds our new column will be added to the display
- To preview it you can scroll all the way to the right
- Confirm its "Display Name" is present ie. "Custom Risk"
Adding a Calculated Column
We repeat steps above but this time we will select "Calculated" from Behavior
- Type the Display Name
- Notice the "Schema name" has been created. In this example it is "dynedge_MyCustomRiskCalculated"
- Select "Date and Time" from the Data type field
- Select "Calculated" from the Behavior
- Note that this needs to be saved in order to access the calculation component to configure it.
- Click Save and edit this will save and launch a pop-up (may need to unblock) to allow editing of the calculation.
- Click the new column
- Click Edit column
- Note that Data type becomes fixed and Behavior is also fixed as calculated
- Click Edit
A window will pop-up and you can then fill out the parameters
- Condition (Optional)
- Action In our case we will have it save the current time when it is created
- Clicking in the box will show a number of different calculation options
- These are the options available out of the box. The one we are going to use is the "NOW()" one. This gets the time NOW.
- Once the command is added we need to save it by checking the check mark
- Save and Close the window
To Test the above you need to create and save a Risk in the app.
Now we can confirm that this has worked as expected.
- Refresh the table to see the new entry, scroll to the right
- Observe the date should be today's date
Adding a custom column to an existing form
In the below example we will walkthrough adding the custom calculated column we added above to the "Risk" form
There are two ways to do this. The old way and the new way.
Old way
Getting into the Advanced Settings from within the "Altus" app
- Open the cog in the top right corner
- Select "Advanced Settings"
Navigating to Solutions
- Click the dropdown menu next to "Settings"
- Click on "Solutions"
Now we need to get to the edit screen for modifying our solution/s
- Click in our case "Customization" this will open the Solution in a new window
- Expand "Entities"
- Expand "Risks" or your corresponding entity
- Expand "Forms"
- Click in our case "Main - Altus" form
- The editing window will be presented
Steps to Add a new column to the form
- Find the requested field in the right hand column. If need be uncheck "Only show used fields" if the field already exists on the form. Then click and drag the field to the column you want it to appear in
- You should see it appear in the column where you release the mouse
- Save the change wait for the blue highlight to disapper
- Publish the change/s
Confirming adding the column has added it to the form
- Navigate to "Projects"
- Select "Risks" in Ribbon in our case
- Select any Risk, in our case "Test"
- Notice that "My Custom Risk Calculated" is present and locked
- Shows todays date
- Shows time you loaded this form as it will update on refresh
- Refresh button to update the value presented as it uses the NOW() time
Removing a selected column from form
- Click the column you wish to remove
- Click the "Remove" button in the ribbon
- Click the "Save" button wait for the blue highlight to disapper
- Click the "Publish" button
New way
Adding the "Altus" model driven app to our "Customization" solution
- Navigate to "Solutions"
- Expand the dots for "Customization" in our case
- Click the "Edit" command
Adding an existing app to the 'Customization' solution
- Select 'Apps'
- Click 'Add existing'
- 'App'
- 'Model-driven app'
Adding existing 'Altus' model-driven app
- Select 'Altus'
- Click 'Add'
Editing the Altus App
- Click the three dots
- Click 'Edit' option
Opening up the New Edit Form
- Scroll down to the required Entity ie. 'Risk' and trigger dropdown
- Hit 'Edit' on 'Risk Forms'
Adding "My Custom Risk Calculated" to New Risk Form
- Click and drag onto the form on the center of the page dropping where you would like it
- In this case its shows a preview of what it will look like
- Save and Publish in the top right hand corner to save it
This will now be visible on the 'New Risk' Form, and because its calculated it will calculate the value each time you refresh the browser window the current time
Creating a Project Type (EPT)
- In the Altus application, navigate to the Settings area where you can access and manage project types.
- Within the Project Types section, click the "New" button in the ribbon to create a new EPT.
- Enter an appropriate name and description for your new EPT to help users understand its purpose.
- In the Form Tabs section, use the radio buttons to select the tabs that should be shown for this EPT. Note that only one Details tab should be marked as visible.
- On the Workflow tab, a Business Process Flow (BPF) must be selected. If the BPF to be associated with this project type has not yet been created or associated with the Altus app, follow the steps in the next section.
- Save the EPT record and ensure that all configurations are saved properly.
- Create a new project using the new EPT to confirm that the BPF displays correctly, all expected tabs are visible, and no duplicate Details pages are present.
Creating a workflow
Custom Workflows can be added to Altus, but there are some important steps required to make them function as expected within Altus.
Create a Business Process Flow (BPF)
Navigate to Solutions
- Navigate to make.powerapps.com and ensure you have the correct environment selected.
- From the left nav menu, select Solutions.
Select the Solution
- Select the unmanaged Solution that contains your enhancements for Altus in your environment.
Create a Process
Select the New button and then select Automation > Process > Business process flow.
Ensure the following details are filled in:
Column Value Display name: {Name of your BPF}Name: {Should be automatically entered for you when you enter the Process name. Adjust if required.}Table: ProjectOnce you have entered the details, click Create.
Populate and Activate Your BPF
- Populate your BPF as per your requirements.
- Once you are happy, Save and Activate your BPF.
Edit or Add a Security Role
- Back within the Altus Enhancements Solution in the Dynamics 365 Advanced Settings area, select to either edit an existing enhancement Security Role, or select to add a new one.
- If adding a new Role, provide a relevant Name and then select the Business Process Flows tab.
- Locate and select the Name of your Business Process Flow. The UI should update to provide security permissions to the role across the permission set.
- Select Save and Close.
Configure the Altus App
Navigate to the Altus App
- You will need the Admin User role to perform these actions
- Select the Settings area of the app
- Select Project Types from the left menu
Create or Edit a Project Type
- Select to create a New Project Type or edit an existing one if you need to change its associated BPF Workflow.
- Enter the details for your new Project Type and the required tabs that you wish to display on the Project Form.
- Select the Workflow tab on the Project Type form.
- In the Workflow Column, search for and locate the BPF that you created earlier.
- Select Save & Close.
Ensure BPF Availability
- Within the Altus Enhancements Solutions in the Dynamics 365 Advanced Settings area, check whether the Altus model driven app is already part of your Enhancements Solution.
- Double-click the Altus model driven app to open it in Edit mode.
- Click the Automation button on the left-nav.
- If your flow is in the Not in this app section of the left-hand pane, click the elipsis next to it and select Add.
- Click the Save icon.
Plugin Registration
Log in to your Environment
- Using the Dynamics Plugin Registration Tool, select to log in to your Environment
- If you have an issue installing the tool please check "nuget sources" and if missing you will need to run "nuget sources add -Name nuget.org -Source https://api.nuget.org/v3/index.json"
Register New Step
- Once connected to your environment, scroll down to locate the SenseiAtsumeruPlugin assembly.
- Right-click (Plugin) SenseiPlugin.Sensei_SetProjectCurrentStage and select Register New Step
- Configure the Step with the following settings, then press Register New Step.
Message Create Primary Table { Select your BPF Table }Secondary Table {none}Filtering Attributes {none}Event Handler (Plugin) SenseiPlugin.Sensei_SetProjectCurrentStageStep Name Sensei.IQ for Project - Set Project Current Stage : Create of { Your BPF Table Name }Run in User's Context Calling UserExecution Order 1Description SenseiPlugin.Sensei_SetProjectCurrentStage: Create of { Your BPF Table Name }Event Pipeline Stage of Execution PostOperationExecution Mode AsynchronousDeployment ServerDelete AsyncOperation if StatusCode = Successful Unchecked
- Once connected to your environment, scroll down to locate the SenseiAtsumeruPlugin assembly.
Register Update Step
- Right-click (Plugin) SenseiPlugin.Sensei_SetProjectCurrentStage again and select Register New Step - this time we will add a handler for the Update operation.
- Configure the Step with the following settings, then press Register New Step:
Message Update Primary Table { Select your BPF Table}Secondary Table {none}Filtering Attributes activestageid, completedon, modifiedonEvent Handler (Plugin) SenseiPlugin.Sensei_SetProjectCurrentStageStep Name Sensei.IQ for Project - Set Project Current Stage : Update of { Your BPF Table Name }Run in User's Context Calling UserExecution Order 1Description SenseiPlugin.Sensei_SetProjectCurrentStage: Update of { Your BPF Table Name }Event Pipeline Stage of Execution PostOperationExecution Mode AsynchronousDeployment ServerDelete AsyncOperation if StatusCode = Successful Unchecked
After configuring these Plugin registration steps, whenever your BPF Table is used (e.g. when a project moves from one stage to another) your project will be updated with the Current Stage value.
Your BPF should now work and display correctly within Altus.
Unhiding a command bar button
Various Command Bar buttons have been hidden from various Tables relating to the Altus Solution. If there is a customer requirement to unhide any of these buttons, then the following procedure can be used:
- Create an Unmanaged Solution (if one does not already exist) in the customer environment and ensure you add to it the existing Tables that you wish to modify the ribbon for.
- Open Ribbon Workbench 2016 (either as web add-on in Dynamics or from the XrmToolbox desktop application).
- Select Open Solution
- Select to open your unmanaged Solution containing the Tables you wish to modify the ribbon for.
- Note: If Ribbon Workbench fails to load your Solution (because your Solution is large), try multiple times. Sometimes this error is transient.
- Once your Solution has loaded, ensure the Command Bar tab is selected
- Next, select the Table that you wish to modify the ribbon for
- Any buttons which have previously been hidden will be shown under the HIDE ACTIONS heading
- To unhide a button, right-click the hide action that you wish to remove and select Un-Hide
- Click OK on the notification. Take note that you will only see the unhidden button after a publish operation (and only in Ribbon Workbench after subsequently reloading the Solution).
- Make the required changes to whichever Tables you wish to modify the command bar for.
- Once you have completed your changes, select Publish
- Click OK to confirm Publish operation.
Dealing with custom tables
As with all customisations to Altus, the recommendation is creating an unmanaged Solution which will then contain your additions and modifications to Altus artifacts. Custom Tables are no different and should be created in your Altus Enhancements solution.
This section provides guidance on how to configure Altus to ensure that your custom Table is treated the same way as out of the box Altus Tables in terms of security and in particular inheriting ownership from the project, program or portfolio that your custom Table is associated with.
The following configuration steps assume that you will have already:
- Created your Custom Table in your enhancements solution
- Added your Custom Table to the project, program and/or portfolio form(s) as a tab with a subgrid
- Created a custom Security Role which provides access to your custom Table
- Added your custom Security Role to the users who require it
- If associated to more than one parent type, we highly recommend creating a Business Rule to ensure that your custom Table records can only relate to one project, program or portfolio at a time. (If you were to relate your custom record to a project and to a portfolio, the ownership of your table record could have unexpected results).
Note
When creating Tables that relate to a project ensure that the relationship between the custom Table and the project table is not set to 'Parental'. Instead, configure the relationship as follows:

If your custom table contains lookup columns to project and/or program and/or portfolio, you will need to configure the Altus Config setting associated with each of those 'parent' Tables to ensure Altus is aware of your custom table.
- As an Altus Admin user, open the app and navigate to the Settings area.
- From the left menu select Configuration Settings.
- Select the {Parent} - Custom Registers setting for the Parent of your custom Table. (Note: You will need to repeat these steps for each parent. e.g. if your custom Table relates to projects and programs, you will need to perform these steps for 'Project - Custom Registers' and 'Program - Custom Registers').

Note
If you do not see the corresponding settings item, switch to the Inactive Config Settings view, locate the setting and switch it to Active.
- Select the New Items button to add your new custom Table configuration.
- Enter details relating to your custom Table to define:
- Table Name: The internal name of your custom Table.
- Required Team Root Role: The Name of your custom security role (this role will be applied to any owner teams).
- Parent Column Name Link: The internal Column name in your custom Table that relates the custom Table to its parent (in the case of a setting for Project - Custom Registers, this would identify the Project Column).
- Assigned To Column: If your custom Table has an Assigned To type Column, identify it by its internal name here.
- Save your Config Settings item
- Repeat these steps for each parent (project, program or portfolio) that relates to your custom Table.
You will also need to perform these next steps to ensure that the Altus Plugin code is triggered to correctly run when you create or update a custom Table record. These Plugin steps ensure that your custom Table records will be visible to members of a project, program or portfolio team (as applicable). To perform these steps, you will need to use the Plugin Registration Tool available here.
- Launch the Plugin Registration Tool (PluginRegistration.exe) and select Create New Connection.
- Ensure you select Office 365, then press Login.
- Enter your credentials to log in to Microsoft 365.
- In the list of Plugin Assemblies, locate (Assembly) SenseiPlugin and click the arrow to expand.
- Depending on your implementation, SenseiAtsumeruPlugin is comparable to SenseiPlugin.
- Depending on your implementation, SenseiAtsumeruPlugin is comparable to SenseiPlugin.
- Locate (Plugin) SenseiPlugin.Sensei_InheritOwnershipFromProject and right-click and select Register New Step.
- Enter the following details, then press Register New Step. This will ensure that whenever a new record is created in your custom Table that the InheritOwnershipFromProject plugin code is run - which will ensure that your custom Table record is attributed the same ownership as the project, program or portfolio that it is associated with.
- Message:
Create - Primary Table:
{Enter your custom Table by its internal name} - Secondary Table:
{Leave blank} - Filtering Attributes:
{Leave blank - unavailable for a Create action} - Event Handler:
{Leave this set to (Plugin) SenseiPlugin.Sensei_InheritOwnershipFromProject} - Step Name:
{Add a name for your Plugin Step, or leave as per the default.} - Run in User's Context:
Calling User - Execution Order:
1 - Description:
{Add a description for your Plugin Step, or leave as per the default.} - Event Pipeline Stage Of Execution:
PreValidation - Execution Mode:
Synchronous - Deployment:
Server
- Message:
Note: Adding an identical Update message step is optional, but should not be required because ownership of the custom entity record should be set correctly by the Create message step and should stay aligned to the parent record's ownership.
- Perform the following steps only if your custom Table has an Assigned To Column which you have defined in the configuration settings. These steps ensure that if the person you have assigned to your Table record is not part of the project/program/portfolio team, then the individual custom Table record will be shared with that person as an individual.
- From the Plugin Registration Tool, right-click (Plugin) SenseiPlugin.Sensei_EnsureAccessForAssignedTo and select Register New Step.
- Enter the following details, then press Register New Step.
- Message:
Create - Primary Table:
{Enter your custom Table by its internal name} - Secondary Table:
{Leave blank} - Filtering Attributes:
{Leave blank - unavailable for a Create action} - Event Handler:
{Leave this set to (Plugin) SenseiPlugin.Sensei_EnsureAccessForAssignedTo} - Step Name:
{Add a name for your Plugin Step, or leave as per the default.} - Run in User's Context:
Calling User - Execution Order:
1 - Description:
{Add a description for your Plugin Step, or leave as per the default.} - Event Pipeline Stage Of Execution:
PostOperation - Execution Mode:
Asynchronous - Deployment:
Server
- Message:
- Again right-click (Plugin) SenseiPlugin.Sensei_EnsureAccessForAssignedTo and select Register New Step.
- Enter the following details, then press Register New Step.
- Message:
Update - Primary Table:
{Enter your custom Table by its internal name} - Secondary Table:
{Leave blank} - Filtering Attributes:
{Select your Assigned To Column} - Event Handler:
{Leave this set to (Plugin) SenseiPlugin.Sensei_EnsureAccessForAssignedTo} - Step Name:
{Add a name for your Plugin Step, or leave as per the default.} - Run in User's Context:
Calling User - Execution Order:
1 - Description:
{Add a description for your Plugin Step, or leave as per the default.} - Event Pipeline Stage Of Execution:
PostOperation - Execution Mode:
Asynchronous - Deployment:
Server - Delete AsyncOperation if StatusCode = Successful:
{Uncheck}
- Message:
- Following best practices, you should add this custom plugin step to your custom Solution.
- Access your Enhancements solution and select Add Existing > More > Developer > Plug-in step, then locate the newly registered step(s).
- Once added, publish all customizations.
- Access your Enhancements solution and select Add Existing > More > Developer > Plug-in step, then locate the newly registered step(s).
Deep deep linking
In release 2021.02.17.2 we introduced the Deep Deep Linking feature to assist with embedding scenarios including Microsoft Teams Tabs. Further information regarding Microsoft Team configuration can be found here: https://hub.sensei.cloud/Docs/Altus/Configuration/Teams/Index.html#teams-app---channel-tab-configuration-setting
To embed a reference to a particular Altus project and tab use the following URL format
https://<orgURL>/main.aspx?appid=<appID>&pagetype=entityrecord&etn=<entityName>&id=<entityID>&navbar=entity&extraqs=sensei_showTab%3D<tabName>%26sensei_hideHeader%3D1
The query string parts are:
- etn: The short form entity name e.g. sensei_project
- id: ID of the entity you wish to embed (guid)
- navbar: set to entity to remove the left-side nav bar
- extraqs: this is the parameter that holds the additionalAltus specific deep deep linking controls
- sensei_showTab: is the name of the tab on the entity you wish to have be displayed. This is the internal name of the tab e.g. tab_tasks sensei_hideHeader: when included and set to 1 this also remove more surrounding chrome from the item.
Example:
https://iq.crm.dynamics.com/main.aspx?appid=e1e44b74-6430-eb11-bf68-000d3a799817&pagetype=entityrecord&etn=sensei_program&id=e436ab00-134d-4eed-bcc9-d3ef9a6dde28&navbar=entity&extraqs=sensei_showTab%3Dtab_risks%26sensei_hideHeader%3D1
Using notes/annotations with project, programs and/or portfolios
The information in this section relates to version 2022.06.07.2 and above of Altus.
Dataverse provides the ability to turn on an attachments/notes capability on each Table/Entity. This can be turned on from the Table properties.
Note
Once this has been turned on, it cannot be switched off again for that Table.
After turning on the Attachments/Notes functionality, you would then need to expose this to the UI by adding a Timeline control to your related Table/Entity form. This will provide the mechanism for users to start adding Notes to the project/program/portfolio.
Altus Security Roles are configured with the necessary permissions for users and owner teams to access Notes. Notes are treated as a special case in Altus and you do not need to configure them in the Custom Registers configuration settings.
However, if you intend to use Attachments/Notes for projects, programs and portfolios, then you will need to configure a Plugin Step that runs on creation of new Notes to ensure that visibility of those Notes flows through correctly into Altus.
To perform these steps, you will need to use the Plugin Registration Tool available here.
- Launch the Plugin Registration Tool (PluginRegistration.exe) and select Create New Connection.
- Ensure you select Office 365, then press Login.
- Enter your credentials to log in to Microsoft 365.
- In the list of Plugin Assemblies, locate (Assembly) SenseiPlugin and click the arrow to expand.
- Depending on your implementation, SenseiAtsumeruPlugin is comparable to SenseiPlugin.
- Depending on your implementation, SenseiAtsumeruPlugin is comparable to SenseiPlugin.
- Locate (Plugin) SenseiPlugin.Sensei_InheritOwnershipFromProject and right-click and select Register New Step.
- Enter the following details, then press Register New Step. This will ensure that whenever a new record is created in your custom Table that the InheritOwnershipFromProject plugin code is run - which will ensure that your custom Table record is attributed the same ownership as the project, program or portfolio that it is associated with.
- Message:
Create - Primary Table:
annotation(this is the Dataverse internal name for the Notes Table/Entity) - Secondary Table:
{Leave blank} - Filtering Attributes:
{Leave blank - unavailable for a Create action} - Event Handler:
{Leave this set to (Plugin) SenseiPlugin.Sensei_InheritOwnershipFromProject} - Step Name:
{Add a name for your Plugin Step, or leave as per the default.} - Run in User's Context:
Calling User - Execution Order:
1 - Description:
{Add a description for your Plugin Step, or leave as per the default.} - Event Pipeline Stage Of Execution:
PreValidation - Execution Mode:
Synchronous - Deployment:
Server
- Message:
Form tab loading
In Kaizen Release - 2022.03.08.4 we have released some form loading improvements to reduce load time and the amount of flashing of tabs and forms. As part of this any conditional tabs have been made hidden to prevent the user from seeing the tabs and then them disappearing, instead we start them hidden and then show the ones that need to be shown.
To make your own custom tabs load smoothly like ours and not impact the user until they are actually required, please hide the tab in the form designer. Note you can make the designer show all hidden controls to allow you to work in the designer with them.
For projects/programs/portfolios you should ensure the tabs are configured appropriately in the form tab visibility settings via the Project Types for Projects or the relevant setting for programs and portfolios.
Form tab visibility exclusions
Form tabs are hidden by default and then programatically shown. If the intention is to hide a form tab (and keep it hidden), Altus includes a Configuration Setting to facilitate this. Under the Security category in Altus Configuration Settings, select the 'Form Tab Visibility Service Exclusions' (FormTabVisibilityExclusions) setting.
To create a new form tab visibility exclusion, select 'New Form Tab'.
Configure the following fields to identify the tab that you wish to exclude:
| Field | Description |
|---|---|
| Entity Name | The Entity/Table that the tab belongs to |
| Tab Internal Name | The Name of the tab that you wish to exclude from the form tab visibility service |
| Form Id | The Form that contains the tab that you wish to exclude from the form tab visibility service |
| Disabled | Can be used to toggle whether the configuration is ignored (if set to Yes, the tab would again be included in the form tab visibility service). |
In the above example, the Decisions tab on the Project Tracking form would be excluded from the Form Tab Visibility Service - and would therefore remain hidden when the Form is displayed to users.
Note
Any tab created with an internal name that has '_ignoreftv' as the suffix will automatically be excluded from the form tab visbility service and does not need to be referenced in this configuration setting.
Role Field Check
Altus can be configured to display Fields, Tabs or Forms according to the security role(s) of the logged in user [including any security roles attributed to a Team that that user is a member of]. This can be configured via the Security > Role Field Check (roleSecurityConfiguration) configuration setting.
The configuration setting deals with three types of items that can be configured to be shown only to users with the appropriate role(s).
- Fields (which can also include sub grids)
- Tabs
- Forms
Fields
| Field | Description |
|---|---|
| Field Name | Identify the internal name of the field, or the internal name of the subgrid that you wish to show based on Security Role(s). |
| Entity Name | Identify the name of the Entity/Table that the field/subgrid relates to. |
| Security Roles | Select the Security Role(s) that the user will require in order to see the field/subgrid. User will see the item if they have any of the selected roles. |
In the above example, the custom field cr123_mycustomfield for the Portfolio entity will only be shown to users if they have the Altus Admin User or Altus - PMO User security role(s).
Tabs
| Field | Description |
|---|---|
| Tab Name | Identify the internal name of the tab that you wish to show based on Security Role(s). |
| Entity Name | Identify the name of the Entity/Table that the tab relates to. |
| Security Roles | Select the Security Role(s) that the user will require in order to see the tab. User will see the tab if they have any of the security roles. |
In the above example, the custom tab tab_mycustomtab for the Program entity will only be shown to users if they have the Altus Admin User or Altus - PMO User security role(s).
Forms
| Field | Description |
|---|---|
| Form Name | Identify the form that you wish to show base on Security Role(s). |
| Entity Name | Identify the name of the Entity/Table that the form relates to. |
| Security Roles | Select the Security Role(s) that the user will require in order to see the form. User will see the form if they have any of the security roles. |
In the above example, the Form called Custom for the Project entity will only be shown to users if they have the Altus Admin User or Altus - PMO User security role(s).
Note
In order for these settings to take effect, the following On Load method must be called from the Form that is being shown to the user: sensei_SenseiProject.Generic.RoleSecurityHandler.FormOnLoad
Many of the out of the box forms shipped with Altus are pre-configured to call this On Load method already (e.g. Projects, Program, Portfolios, etc).
Out of the box behaviour can be altered by selecting to Load Defaults from the configuration setting form for the Role Field Check setting. This will load the default values used internally within Altus and those settings can then be overridden.
Project form UI
The Project Form UI update includes changes to the Project Form which split tab content out across multiple Forms, ensuring that projects are easy to navigate. With this update comes the introduction of 'link tabs' which provide easy navigation between Project Forms.
Create a project form
A new custom Project Form can be added in an Altus environment and integrated with the Project Form UI functionality.
- Create a new Form for the Project Table and add the content to it that you wish to display to users.
- Ensure that your custom Project Form references the following Form On Load events:
If New Record Switch To Form
| Event Type | On Load |
| Library | sensei_SenseiProject.bundle.js |
| Function | sensei_SenseiProject.Generic.FormSwitcher.IfNewRecordSwitchToForm |
| Enabled | {checked} |
| Pass execution context as first parameter | {checked} |
| Comma separated list of parameters... | "59100881-fae9-4d5b-b520-44f769b21f6a" |
Note
Ensure that you wrap the parameter (which contains the form Id of the Project Information form) in quotes
Redirect Non-Default Form On Load
| Event Type | On Load |
| Library | sensei_SenseiProject.bundle.js |
| Function | sensei_SenseiProject.Generic.FormSwitcher.RedirectNonDefaultFormOnLoad |
| Enabled | {checked} |
| Pass execution context as first parameter | {checked} |
| Comma separated list of parameters... | "59100881-fae9-4d5b-b520-44f769b21f6a" |
Create Link Tabs on your custom Project Form which redirect the user to the other Project Forms.
See: How to Create Link TabsCreate Link Tabs on the out of the box Project Forms which redirect the user to your custom Project Form.
See: How to Create Link Tabs
How to create link tabs
- To create a Link Tab on a Form, select to edit the Form you wish to add the link tab to.
- Then, select to add a new Tab to your Form. Give the tab an internal name something in the format of 'tab_link_' (e.g. tab_link_mycustomform). If your custom form has spaces in its display name, ensure that you replace those spaces with underscores in the internal link tab name.
- Add an On Tab State Change event to that Tab, as follows:
| Event Type | On Tab State Change |
| Library | sensei_SenseiProject.bundle.js |
| Function | sensei_SenseiProject.Generic.FormSwitcher.FormOnTabStateChange |
| Enabled | {checked} |
| Pass execution context as first parameter | {checked} |
| Comma separated list of parameters... | "*{Id or Name of the Form you wish to redirect to when the tab is selected}*" |
Note
Ensure that you wrap the parameter in quotes
Finance app data locking
There are Yes/No fields which can be set to lock fields for editing in the Finance UI.
The fields can be set on the following entities:
- Project (sensei_project)
- Project (msdyn_project) [for Altus PFTW Edition]
- Financial Item (sensei_financialitem)
In each case the fields are named as follows:
- Actual Cost Locked (sensei_actualcostlocked)
- Budget Locked (sensei_budgetlocked)
- Forecast Cost Locked (sensei_forecastcostlocked)
These Yes/No fields are not present on any input forms, they can currently only be set programmatically. Once set, users will be restricted from modifying the corresponding data in the UI. (e.g. if Budget Locked is set at the project level, users will not be able to modify Budget values across the entire project).
Date field formats
When creating Date based fields in a Dataverse Table, there are 2 primary options and each of those contains additional options.
The primary date types are Date Time and Date Only. By default when you create either of these field types, the Behavior field will default to 'User local'. You can optionally change this Behavior setting - but note that if you do, the UI will prevent you from changing it again.
Brief description of the types and how they might be used:
| Field Type | Behavior | Use Cases |
|---|---|---|
| Date Time | User local | This will store the date and time relative to the user who entered the data. The data is then presented to other users according to their own timezone - or as UTC via web services. This field type can be used to indicate the date/time of an event that occurred. E.g. When an approval action occurred. |
| Date Time | Timezone Independent | This stores the date and time that was entered by a user independent of that user's timezone. E.g. If a user enters 1/1/2022 at 7pm then that same date and time will be present to any/all users regardless of that user's timezone. |
| Date Only | User local | Behind the scenes still stores a Date Time but presents just the date to a user via the UI. The date is translated into the viewing user's timezone. (So would appear as 2022-01-01 in Australia but 2021-31-12 in US). Can't think of too many cases where you would want this behaviour. |
| Date Only | Date only | Behind the scenes still stores a Date Time, but the time component will always be stored as midnight and will present to the user regardless of their timezone. This setting is useful for when a user selects a Date value in the UI and expects other users to see that same date value. |
| Date Only | Timezone Independent | Again still stores a Date Time value and can be used to store a time component other than midnight (presumably programmatically as you cannot set it through the UI), and the same Date will be presented to all users regardless of their timezone. |
Known Altus Solution Upgrade Failures
Managed Layers on Cloud Flows
If you are encountering issues with solution updates and see errors like the one following:
<Message>Workflow uninstall: FAILURE, workflow id 8b3c54b8-8419-eb11-a813-000d3a7946d7 category ModernFlow name Proposal Approval Atsumeru </Message>
The error is due to a managed layer sitting on top of the OOTB (out of the box) Altus cloud flow. As part of the standard Dataverse upgrade process the layer must be removed and as there is a layer on top of which depends on this flow, this action is not possible.
This can be resolved by deploying the customisation solution to the target environment without the cloud flows. Remove the managed layer on the flow, then apply the upgrade, then re-introducing the customisations on that flow by importing the solution back in. To avoid this error, it is recommended that the OOTB cloud flows are not customised, but instead copied and altered respectively.