Social

onsdag den 16. april 2014

Nested Type Projections in Service Manager

Displaying the review username in a review activity contained within a service reuqest in a view is actually pretty straight forward. (With a little help from Morten@Coretech)
First we create a type projection on the review activity class with a nested component. Create a fresh management pack using the console or authoring tool, and open it using your favorite XML-editor. We will need a reference to the Activity Library

<Reference Alias="ActivityLibrary">
    <ID>System.WorkItem.Activity.Library</ID>
    <Version>7.5.3079.0</Version>
    <PublicKeyToken>31bf3856ad364e35</PublicKeyToken>
</Reference>

After the Manifest closing tag we add

<TypeDefinitions>
    <EntityTypes>
        <TypeProjections>

        </TypeProjections>
    </EntityTypes>
</TypeDefinitions>

All pretty standard. A typeprojection looks like

<TypeProjection ID=”TypeProjection.ReviewActivity.Something” Accessibility=”Public” Type=ActivityLibrary!System.WorkItem.Activity.ReviewActivity”>

But how to pick the type? We know we want to target the review activity, and we use the reference alias ActivityLibrary. Lets look in that one. Export it using the console and open it up and look inside.
There is a bunch of ClassTypes defined in that MP, and exactly this is the ones we can choose from (you can even pick the abstract ones, System.WorkItem.Activity will target all class that are based on it allowing you to build a view showing all activities). Makes sense right?

Next we can add components to the type projection which are used to expose the relationships the targeted type can be part of. In this case you look further down in ActivityLibrary. The relationship type with ID System.WorkItemContainsActivity is easy to understand. The source is System.WorkItem and target is System.WorkItem.Activity, exactly as the ID suggests. The lowest possible cardinality is 0 for both source and target; The source can contain no activities, and the target does not need to be contained in a work item, (although they would be in any practical regards).  You can create a "standalone" manual activity using SMLets:
Import-Module SMLets;
New-SCSMObject -Class (Get-SCSMClass system.workitem.activity.manualactivity$) -PropertyHashtable @{}

MaxCardinality tells us how many relationships there can be at most to/from the target/source. If the value is greater than 1 it will appear as multivalued in the view editor, and you will need to create the display column in XML. The System.WorkItemContainsActivity relationship type is a many-to-many relationship, that is the activity can be contained in more than a single work item (again not in any practical regard Note: Not validated) and a work item can contain more than a single activity. Often the target cardinality limits are of interest.

The component is defined by an Alias and a Path

<Component Alias="Activity" Path="$Target/Path[Relationship='ActivityLibrary!System.WorkItemContainsActivity' TypeConstraint='ActivityLibrary!System.WorkItem.Activity.ReviewActivity']$" >
</Component>

, where Relationship is picked from one of the relationship types in the referenced MP. The TypeConstraint is optional and is used to narrow the component to be a specific classtype, in this case a review activity.

This component exposes all review activities which are related to the type defined in the typeprojection (limited by the target and source types defined in the relationship type). We can also nest the component. If we wanted to show review activities contained in a service request we would do:

<TypeProjection ID="TypeProjection.ServiceRequestContainingReviewActivity" Accessibility="Public" Type="ServiceRequestLibrary!System.WorkItem.ServiceRequest">
     <Component Alias="Activity" Path="$Target/Path[Relationship='ActivityLibrary!System.WorkItemContainsActivity' TypeConstraint='ActivityLibrary!System.WorkItem.Activity.ReviewActivity']$" ></Component>
</TypeProjection>

If we wanted to do something with the reviewers of the review activity we could add a nested component:

<TypeProjection ID="TypeProjection.ServiceRequestContainingReviewActivityReviewer" Accessibility="Public" Type="ServiceRequestLibrary!System.WorkItem.ServiceRequest">
     <Component Alias="Activity" Path="$Target/Path[Relationship='ActivityLibrary!System.WorkItemContainsActivity' TypeConstraint='ActivityLibrary!System.WorkItem.Activity.ReviewActivity']$" >
        <Component Alias="Reviewer" Path="$Target/Path[Relationship='ActivityLibrary!System.ReviewActivityHasReviewer']$" />
     </Component>
</TypeProjection>

Problem is that the reviewer is of type System.Reviewer which is not the actual user but a sort of cointainer with a relationship to System.User (we need to add a component with relationship type System.ReviewerIsUser). System.User is defined in System.Library (export from the console and click no in the dialog box that appears).

We can add an additional component to the Review component

<Component Alias="User" Path="$Target/Path[Relationship='ActivityLibrary!System.ReviewerIsUser']$" />

The final type projection will look something like

<TypeProjection ID="TypeProjection.ServiceRequestContainingReviewActivityReviewer" Accessibility="Public" Type="ServiceRequestLibrary!System.WorkItem.ServiceRequest">
     <Component Alias="Activity" Path="$Target/Path[Relationship='ActivityLibrary!System.WorkItemContainsActivity' TypeConstraint='ActivityLibrary!System.WorkItem.Activity.ReviewActivity']$" >
        <Component Alias="Reviewer" Path="$Target/Path[Relationship='ActivityLibrary!System.ReviewActivityHasReviewer']$" >
            <Component Alias="User" Path="$Target/Path[Relationship='ActivityLibrary!System.ReviewerIsUser']$" />
        </Component>
     </Component>
</TypeProjection>

Try creating a view based on this type projection. I suggest using Advanced View Editor. The Contains Activity is multivalued (remember the cardinality discussed earlier?), and so is the Reviewers relationship, hence in order to display these valued in a view we need to do it manually in XML. Save the view and exported the MP it is saved in. Open the MP in an XML editor. Scroll down to the view and find the mux:Column tag. There is likely a Title and Id defined already. Copy one of those lines and change the tag properties as follows

  • Name="User 1"
  • DisplayMemberBinding="{Binding Path=Activity[0].Reviewer[0].User.DisplayName"
  • DisplayName="User1.X1"
  • Property="Activity[0].Reviewer[0].User.DisplayName"
Where X1 is a GUID. An easy way to generate a GUID is using Powershell

[guid]::NewGuid().Guid.Replace("-","") | Clip

This will place the newly generated GUID in your clipboard.

<mux:Column Name="User 1" DisplayMemberBinding="{Binding Path=Activity[0].Reviewer[0].User.DisplayName}" DisplayName="User1.ee1a1dfc4e67497abe228bc9aa3e10ea" Property="Activity[0].Reviewer[0].User.DisplayName" DataType="s:String" />

The Binding Path needs a bit of explaining. Activity is a component alias. As this relationsship has a max cardinaly greater than 1 we pick the very first relationship (index 0), ie. the first activity contained in the service request. You did remember to type constrain the component to the ReviewActivity class right? If not and the relationship is to ex. a manual activity there is no Reviewer relationship and nothing is displayed.
We also pick the first Reviewer relationship between the review activity and reviewer as this also has a max cardinality of greater than 1. Lastly we pick the User component alias. This relationship has a max cardinality of 1, hence we do not, and should not index it. Finally we pick the property DisplayName defined on System.User (defined in System.Library).
The Property tag is almost identical to the DisplayMemberBinding and is used for sorting the column (in conjunction with DataType) - let me just refer you to a neat trick by Anders Asp, Sorting a view by ID (doesn't work as expected out of the box).

Also create a new Viewstring (You will need yet another GUID for this)

<ViewString ID="User1.ee1a1dfc4e67497abe228bc9aa3e10ea">$MPElement[Name="User1.fe970b23bfd64d3ab9c4176785a991d3"]$</ViewString>

And add to StringRessources

<StringResource ID="User1.fe970b23bfd64d3ab9c4176785a991d3" />

And we can finally name the column

<DisplayString ElementID="User1.fe970b23bfd64d3ab9c4176785a991d3">
  <Name>Review User</Name>
</DisplayString>

Import the MP in the console and restart the console. Enjoy your new view!

The type projections shown so far are from source to target, ie. from service request to activity. We can do it the other way around. The reverse of the type projection illustrated above is

<TypeProjection ID="TypeProjection.ServiceRequestContainingReviewActivityReviewerUserReversed" Accessibility="Public" Type="System!System.User">
    <Component Alias="Reviewer" Path="$Context/Path[Relationship='ActivityLibrary!System.ReviewerIsUser' SeedRole='Target']$" >
        <Component Alias="ParticipatesInReviewActivity" Path="$Context/Path[Relationship='ActivityLibrary!System.ReviewActivityHasReviewer' SeedRole='Target']$" >
            <Component Alias="ServiceRequest" Path="$Context/Path[Relationship='ActivityLibrary!System.WorkItemContainsActivity' SeedRole='Target' TypeConstraint='ServiceRequestLibrary!System.WorkItem.ServiceRequest']$" />
        </Component>
    </Component>
</TypeProjection>

Basically the $Target/Path is replaced with $Context/Path and the direction is defined by SeedRole='Target'. The mux:Column in a view using this type projection looks like

<mux:Column Name="ServiceRequest" DisplayMemberBinding="{Binding Path=Reviewer[0].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName}" DisplayName="ServiceRequest.94e76a6abfe84fb4a1a2ad86fca7e1fb" Property="Reviewer[0].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName" DataType="s:String" />

Note that all component aliases are indexed as the cardinality for these are all greater than 1. In short this shows service requests and users that are involved in reviewing in these.

/Happy easter!

edit:
Added a few good screenshots, and some clarification on the "reverse view". The reviewing user and service request title is purely coincidental!


How the first view could look like

How the second view could look like
The reverse view would need multiple column as a given user could be reviewing in more than one service request. This can be achieved by the XML-code below

<mux:Column Name="ServiceRequest" DisplayMemberBinding="{Binding Path=Reviewer[0].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName}" DisplayName="ServiceRequest.94e76a6abfe84fb4a1a2ad86fca7e1fb" Property="Reviewer[0].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName" DataType="s:String" />
 <mux:Column Name="ServiceRequest1" DisplayMemberBinding="{Binding Path=Reviewer[1].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName}" DisplayName="ServiceRequest1.94e76a6abfe84fb4a1a2ad86fca7e1fb" Property="Reviewer[1].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName" DataType="s:String" />
 <mux:Column Name="ServiceRequest2" DisplayMemberBinding="{Binding Path=Reviewer[2].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName}" DisplayName="ServiceRequest2.94e76a6abfe84fb4a1a2ad86fca7e1fb" Property="Reviewer[2].ParticipatesInReviewActivity[0].ServiceRequest[0].DisplayName" DataType="s:String" />

That is there can be more Reviewers for each user (Reviewer is a container with details on how the user should review the activity).

#SCSM, #Type projections, #Service Manager, #Nested, #View, #2012, #Microsoft, #System Center

Ingen kommentarer:

Send en kommentar

Søg i denne blog