Thursday, November 9, 2017

Dynamic ssrs reports

Sometimes we dont know the number of columns in a ssrs report at design time and would like to be able to generate columns and rows dynamically during runtime similar to crystal reports.

Though this is not directly possible with SSRS report builder (or visual studio SSRS reports project), the workaround is to generate .rdl file dynamically. rdl files are xml files and hence can be easily generated using code.

The following download is one such sample C# project.

http://gotreportviewer.com/DynamicTable.zip

Monday, October 2, 2017

Knockout options group binding with JS objects

If we need to bind complex JS objects to select HTML element in knockout with optgroups then the following jsfiddle has great sample

http://jsfiddle.net/rniemeyer/aCS7D/

so the HTML binding looks like:

<select data-bind="foreach: groups, value: selectedOption">
    <optgroup data-bind="attr: {label: label}, foreach: children">
        <option data-bind="text: label, option: $data"></option>
    </optgroup>
</select>

       
<hr />

<div data-bind="text: specialProperty"></div>

and js code looks like:

ko.bindingHandlers.option = {
    update: function(element, valueAccessor) {
       var value = ko.utils.unwrapObservable(valueAccessor());
       ko.selectExtensions.writeValue(element, value); 
    }       
};

function Group(label, children) {
    this.label = ko.observable(label);
    this.children = ko.observableArray(children);
}

function Option(label, property) {
    this.label = ko.observable(label);
    this.someOtherProperty = ko.observable(property);
}

var ViewModel = function() {
    this.groups = ko.observableArray([
        new Group("Group 1", [
            new Option("Option 1", "A"),
            new Option("Option 2", "B"),
            new Option("Option 3", "C")
        ]),
        new Group("Group 2", [
            new Option("Option 4", "D"),
            new Option("Option 5", "E"),
            new Option("Option 6", "F")
        ])
    ]);

    this.selectedOption = ko.observable();

    this.specialProperty = ko.computed(function(){
        var selected = this.selectedOption();
        return selected ? selected.someOtherProperty() : 'unknown';
    }, this);
};

ko.applyBindings(new ViewModel());

Tuesday, September 19, 2017

SSAS - MDX: Time intelligence dimensions


In SSAS cube there is a frequent need to calculate statistical functions like sum, average, growth, growth percentage across various time periods. Some examples are listed below:


  • Average growth over last year
  • Total debt in previous financial year
  • Year on year growth for each month in financial year
  • Monthly rolling sum
  • Year to date sales
  • Quarter on quarter order count growth
Such calculations can be coded in a SSAS cube for each of the measures separately, but this is a time consuming and repeating task. It also makes the script much longer and difficult to understand.

There are some alternative approaches which enable us to code these calculations in a generic way so that they are applied across all measures automatically. This approach is explained at:




And finally there is a very good approach using which we can code in calculations in "two dimensions". First dimension lists "time aggregation" functions and the second dimension lists "calculation" to be done,  and then they are cross joined to provide the final set of calculations. For example the list of above calculations can be divided in two dimensions like:

  • Aggregations
    • Last Year
    • Current Quarter
    • Current Month
    • Current Year to Date
    • Rolling Last 12 months
    • Quarter over Last Year Quarter
  • Calculations
    • Sum
    • Average
    • Growth
    • Growth %
This remarkable approach makes the calculation script concise and well organized. It is little more difficult to imagine however. The blog below explains this method and we can also download Adventure works cube solution.


Friday, July 28, 2017

SharePoint export and import terms

SharePoint has a great feature to set terms which can be used to tag contents and enable terms based navigation and several other interesting purposes.

Often times we need to migrate terms from one environment to the other and there is not OOB option to export terms in SharePoint 2013. This is not very difficult with powershell however, and this excellent article makes the scripts available for us:

https://blogs.msdn.microsoft.com/thirusrinivasan1/2015/09/28/sharepoint-exportimport-managed-metadata-terms-through-csv/

While trying to use the above scripts I noticed that the import script checks for IsPinned property on the term to be added and in our environment this property was null rather than false. So I have modified that script slightly and reposting both the script for future reference.

Export Terms




# Outputs CSV of the specified termset from the specificed termstore/group
# Example call:
# .\ExportMMStoCsv.ps1 "https://contoso.com:12345" "Managed Metadata Service"
param ([string]$centralAdminUrl = "https://contoso.com:12345",
[string] $termStoreName = "Managed Metadata Service"
)
Add-PSSnapin microsoft.sharepoint.powershell

$maxLevels = 5

function Export-SPTermStoreGroupTerms()
{

    $isValid = $true;
    $message = "";

    if ($centralAdminUrl.Length -eq 0) { $message = "Please provide a central admin URL"; $isValid = $false; }

    if ($isValid -eq $false)
    {
        write-host "ERROR OCCURRED`t$message"
        write-host "NAME`tExport-SPTermStoreGroupTerms"
        write-host "SYNOPSIS`tReturns a CSV file containing a listing of term names and identifiers from the supplied term set."
        write-host "SYNTAX`tExport-SPTermStoreGroupTerms centralAdminUrl termStoreName termGroupName termSetName outPutDir"
        write-host "EXAMPLES Export-SPTermStoreGroupTerms ""http://sp2010"" ""Managed Metadata Service"" ""Enterprise Metadata"" ""Business Units"""
        return;
    }

    try
    {
        $ErrorActionPreference = "Stop";

        try
        {
            $site = Get-SPSite $centralAdminUrl;
            $taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);

            try
            {
                $termStore = $taxSession.TermStores[$termStoreName];

                if ($termStore -ne $null)
                {
                    try
                    {
                        $filename = $centralAdminUrl.Replace("https://","").Replace("/","").Replace(":","")
                        $time = (Get-Date).tostring("ddMMyyyy_hhmmss")
                        $outPutFile =  (Get-Location).Path + [string]::Format("\{0}_{1}.csv", $filename,$time);
                        $sb = new-object System.Text.StringBuilder;
                        $sb.Append("TermGroup, TermSet,Term,Level2, Level3,Level4,Level5,UpdatedTitle,Url,FriendlyUrl,CatalogTargetUrl,CatalogChildTargetUrl,TargetUrl,ChildTargetUrl,LastModified,Action,Status");

                        [Byte[]] $ampersand = 0xEF,0xBC,0x86;

                        foreach ($termGroup in $termStore.Groups)
                        {
                            $termgroupName =  $termGroup.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                            if ($termgroupName.equals("system", [stringcomparison]::OrdinalIgnoreCase) -eq $false)
                            {
                                try
                                {
                                    foreach($termSet in $termGroup.TermSets)
                                    {
                                        $termsetName  = $termSet.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                                        foreach ($term in $termSet.Terms)
                                        {
                                            write-host $termgroupName ","$termsetName","$termName
                                            $termName = $term.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                                            $custProp1 = $term.LocalCustomProperties["_Sys_Nav_SimpleLinkUrl"]
                                            $custProp2 = $term.LocalCustomProperties["_Sys_Nav_FriendlyUrlSegment"]
                                            $custProp3 = $term.LocalCustomProperties["_Sys_Nav_CatalogTargetUrl"];
                                            $custProp4 = $term.LocalCustomProperties["_Sys_Nav_CatalogTargetUrlForChildTerms"];
                                            $custProp5 = $term.LocalCustomProperties["_Sys_Nav_TargetUrl"];
                                            $custProp6 = $term.LocalCustomProperties["_Sys_Nav_TargetUrlForChildTerms"];
                                            $custProp7 = $term.LastModifiedDate.ToString("MM/dd/yyyy HH:mm:ss");

                                            $sb1 = new-object System.Text.StringBuilder;
                                            addOutPutField -sb1 $sb1 -field $termgroupName
                                            addOutPutField -sb1 $sb1 -field $termsetName
                                            addOutPutField -sb1 $sb1 -field $termName
                                          
                                            $path = $sb1.ToString();
                                            addEmptyFields -sb1 $sb1 -count 5
                                           
                                            addOutPutField -sb1 $sb1 -field $custProp1
                                            addOutPutField -sb1 $sb1 -field $custProp2
                                            addOutPutField -sb1 $sb1 -field $custProp3
                                            addOutPutField -sb1 $sb1 -field $custProp4
                                            addOutPutField -sb1 $sb1 -field $custProp5
                                            addOutPutField -sb1 $sb1 -field $custProp6
                                            addOutPutField -sb1 $sb1 -field $custProp7
                                            addEmptyFields -sb1 $sb1 -count 2

                                            $sb.AppendLine();
                                            $sb.Append($sb1.ToString());
                                            $sb1.Clear()
                                            GetChildTerms -term $term -path $path -sb $sb
                                        }                                   
                                    }
                                }
                                catch
                                {
                                    "Unable to acquire the termset from the term group"
                                }
                            }
                        }

                        $sw = new-object system.IO.StreamWriter($outPutFile);
                        $sw.Write($sb.ToString());
                        $sw.close();
                        write-host "Your CSV has been created at $outPutFile";
                    }
                    catch
                    {
                        "Unable to acquire term store group"
                    }
                }
            }
            catch
            {
                "Unable to acquire term store"
            }
        }
        catch
        {
            "Unable to acquire session for the site $centralAdminUrl"
        }
    }
    catch
    {

    }
    finally
    {
        $ErrorActionPreference = "Continue";
    }
}

function addOutPutField($sb1, $field){
    $val = "";
    if ($field)
    {
        $val = $field;
    }

    if($sb1.Length -gt 0)
    {
        $sb1.AppendFormat(",{0}",$val);
    }
    else
    {
        $sb1.Append($val);
    }
}

function addEmptyFields($sb1, $count){
    for($i=1; $i -le $count; $i++)
    {
        $sb1.Append(",");
    }
}

function GetChildTerms($term, [object]$path, $sb){
    if ($term.TermsCount -gt 0)
    {
        foreach ($childterm in $term.terms)
        {
            $termName = $childterm.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")
            $custProp1 = $childterm.LocalCustomProperties["_Sys_Nav_SimpleLinkUrl"]
            $custProp2 = $childterm.LocalCustomProperties["_Sys_Nav_FriendlyUrlSegment"]
            $custProp3 = $childterm.LocalCustomProperties["_Sys_Nav_CatalogTargetUrl"];
            $custProp4 = $childterm.LocalCustomProperties["_Sys_Nav_CatalogTargetUrlForChildTerms"];
            $custProp5 = $childterm.LocalCustomProperties["_Sys_Nav_TargetUrl"];
            $custProp6 = $childterm.LocalCustomProperties["_Sys_Nav_TargetUrlForChildTerms"];
            $custProp7 = $childterm.LastModifiedDate.ToString("MM/dd/yyyy HH:mm:ss");

            $sb11 = new-object System.Text.StringBuilder;
            addOutPutField -sb1 $sb11 -field $path
            addOutPutField -sb1 $sb11 -field $termName
            [object] $childpath = $sb11.ToString();
            $fields = $sb11.ToString().split(',')
            addEmptyFields -sb1 $sb11 -count ($maxLevels - $fields.length + 3)
            addOutPutField -sb1 $sb11 -field $custProp1
            addOutPutField -sb1 $sb11 -field $custProp2
            addOutPutField -sb1 $sb11 -field $custProp3
            addOutPutField -sb1 $sb11 -field $custProp4
            addOutPutField -sb1 $sb11 -field $custProp5
            addOutPutField -sb1 $sb11 -field $custProp6
            addOutPutField -sb1 $sb11 -field $custProp7
            addEmptyFields -sb1 $sb11 -count 2

            $sb.AppendLine();
            $sb.Append($sb11.ToString());
            write-host $childpath;
            $sb11.Clear()
            #$sw.writeline($towrite);
           
            GetChildTerms -term $childterm -path $childpath -sb $sb
        }
    }

    $path = "";
}

Export-SPTermStoreGroupTerms


Wednesday, July 26, 2017

Text truncate in bootstrap

The css property text-overflow: ellipsis; works well for containers having width setting other than percentage, however since most responsive frameworks like Bootstrap set widths as a percentage value, it becomes somewhat more difficult to achieve this behavior.

A very nice alternative as suggested at http://collaboradev.com/2015/03/28/responsive-css-truncate-and-ellipsis/#codesyntax_2  is to add another div inside our container element with css properties display: table;  and table-layout: fixed;


.truncate-ellipsis {
    display: table;
    table-layout: fixed;
    width: 100%;
    white-space: nowrap;
}
 
.truncate-ellipsis > * {
    display: table-cell;
    overflow: hidden;
    text-overflow: ellipsis;
}

<div class="my-container">
<div class="truncate-ellipsis">
<span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porta tortor vitae nisl tincidunt varius. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel ullamcorper tortor. Nullam vel turpis a augue tempor posuere vel quis nibh. Nam ultrices felis turpis, at commodo ipsum tristique non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse in accumsan dui, finibus pharetra est. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vitae velit eu dui rutrum pellentesque vel imperdiet sem. Morbi ut lacinia lacus, in commodo nibh. Sed cursus ante ut nunc molestie viverra. </span>
</div>
</div>

Tuesday, July 25, 2017

MDX Filter dimension members and attributes

Sometimes we need to filter members of a dimension based on value of one attribute and fetch value of another attribute of the same member.

This is relatively simple in a query, like so:

WITH
   MEMBER [Measures].[Label] AS [Geography].[Country].CURRENTMEMBER.MEMBER_CAPTION
SELECT {[Measures].[Label]} ON COLUMNS,
       NON EMPTY { ([Geography].[State-Province].&[FL]&[US] ) } ON ROWS

FROM  [Adventure Works]

However notice that we need to know the country code 'US' to specify state Florida's value. The query gets even more complex when we only know state code 'FL' and want to use the country label as a calculated measure. Here we need to filter the dimension members based on one attribute value and return caption of another attribute of the same dimension member.

So the query changes to:

WITH
   MEMBER [Measures].[Label] AS (filter(
   [Geography].[Country].children * [Geography].[State-Province].children, Instr( [Geography].[State-Province].currentmember.Properties( 'Member_Caption' ), 'FL')  > 0)
   ).item(0).item(0).membervalue

SELECT {[Measures].[Label]} ON COLUMNS
FROM  [Adventure Works]

And the result is:


More reference on querying dimension members:

Secure micro services using jwt and ocelot

  Secure Microservices Using JWT With Ocelot in .NET Core (code-maze.com)