diff --git a/TabRESTMigrate/TabRESTMigrate.csproj b/TabRESTMigrate/TabRESTMigrate.csproj index 97dce8c..8a7393c 100644 --- a/TabRESTMigrate/TabRESTMigrate.csproj +++ b/TabRESTMigrate/TabRESTMigrate.csproj @@ -23,7 +23,7 @@ false true publish.htm - 12 + 13 1.0.0.%2a false true diff --git a/TabRESTMigrate/TaskManager/TaskMaster.cs b/TabRESTMigrate/TaskManager/TaskMaster.cs index 0330308..15ee2e6 100644 --- a/TabRESTMigrate/TaskManager/TaskMaster.cs +++ b/TabRESTMigrate/TaskManager/TaskMaster.cs @@ -1148,7 +1148,7 @@ private void Execute_GenerateSiteInventoryFile_Twb(string pathReportCsv) var twbGenerateFromTemplate = new TwbReplaceCSVReference( PathHelper.GetInventoryTwbTemplatePath(), //*.twb we are using as our template pathTwbOut, //Output *.twb we are generating - "siteInventory", //Datasource name in tempalte workbook + "siteInventoryTemplate.csv", //Datasource filename in tempalte workbook pathReportCsv, //CSV file we want to associate with the datasource above _statusLog); diff --git a/TabRESTMigrate/WorkbookTransforms/TwbReplaceCSVReference.cs b/TabRESTMigrate/WorkbookTransforms/TwbReplaceCSVReference.cs index 2bec89e..b731a56 100644 --- a/TabRESTMigrate/WorkbookTransforms/TwbReplaceCSVReference.cs +++ b/TabRESTMigrate/WorkbookTransforms/TwbReplaceCSVReference.cs @@ -12,7 +12,7 @@ class TwbReplaceCSVReference { private readonly string _pathToTwbInput; private readonly string _pathToTwbOutput; - private readonly string _datasourceName; + private readonly string _oldDatasourceFilename; private readonly string _datasourceNewCsvPath; private readonly TaskStatusLogs _statusLog; @@ -21,14 +21,14 @@ class TwbReplaceCSVReference /// /// TWB we are going to load and transform /// Output path for transformed CSV - /// Name of data source inside path + /// Old filename for the data source (case insensitive) /// Path to CSV file that we want the data source to point to /// Log status and errors here - public TwbReplaceCSVReference(string pathTwbInput, string pathTwbOutput, string dataSourceName, string newCsvPath, TaskStatusLogs statusLog) + public TwbReplaceCSVReference(string pathTwbInput, string pathTwbOutput, string oldDatasourceFilename, string newCsvPath, TaskStatusLogs statusLog) { _pathToTwbInput = pathTwbInput; _pathToTwbOutput = pathTwbOutput; - _datasourceName = dataSourceName; + _oldDatasourceFilename = oldDatasourceFilename; _datasourceNewCsvPath = newCsvPath; _statusLog = statusLog; } @@ -45,7 +45,7 @@ public bool Execute() xmlDoc.Load(_pathToTwbInput); bool foundReplaceItem = - RemapDatasourceCsvReference(xmlDoc, _datasourceName, _datasourceNewCsvPath, _statusLog); + RemapDatasourceCsvReference(xmlDoc, _oldDatasourceFilename, _datasourceNewCsvPath, _statusLog); //Write out the transformed XML document TableauPersistFileHelper.WriteTableauXmlFile(xmlDoc, _pathToTwbOutput); @@ -56,17 +56,16 @@ public bool Execute() /// Finds and changes a datasource reference inside a Workbook. Changes the CSV file the data source points to /// /// - /// + /// Filenane (without path) of the datasource we want to replace. Case insensitive /// /// - private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string datasourceName, string pathToTargetCsv, TaskStatusLogs statusLog) + private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string oldDatasourceFilename, string pathToTargetCsv, TaskStatusLogs statusLog) { int replaceItemCount = 0; string newCsvDirectory = Path.GetDirectoryName(_datasourceNewCsvPath); string newCsvFileName = Path.GetFileName(_datasourceNewCsvPath); string newDatasourceRelationName = Path.GetFileNameWithoutExtension(newCsvFileName) + "#csv"; string newDatasourceRelationTable = "[" + newDatasourceRelationName + "]"; - string seekDatasourceCaption = _datasourceName; var xDataSources = xmlDoc.SelectNodes("workbook/datasources/datasource"); if(xDataSources != null) @@ -74,29 +73,54 @@ private bool RemapDatasourceCsvReference(XmlDocument xmlDoc, string datasourceNa //Look through the data sources foreach (XmlNode xnodeDatasource in xDataSources) { - //If the data source is matching the caption we are looking for - if(XmlHelper.SafeParseXmlAttribute(xnodeDatasource, "caption", "") == seekDatasourceCaption) + var xConnections = xnodeDatasource.SelectNodes(".//connection"); + if (xConnections != null) { - var xnodeConnection = xnodeDatasource.SelectSingleNode("connection"); - //It should be 'textscan', it would be unexpected if it were not - if(XmlHelper.SafeParseXmlAttribute(xnodeConnection, "class", "") == "textscan") + foreach (XmlNode xThisConnection in xConnections) { - //Point to the new directory/path - xnodeConnection.Attributes["directory"].Value = newCsvDirectory; - xnodeConnection.Attributes["filename"].Value = newCsvFileName; - - //And it's got a Relation we need to update - var xNodeRelation = xnodeConnection.SelectSingleNode("relation"); - xNodeRelation.Attributes["name"].Value = newDatasourceRelationName; - xNodeRelation.Attributes["table"].Value = newDatasourceRelationTable; - - replaceItemCount++; - } - else - { - _statusLog.AddError("Data source remap error. Expected data source to be 'textscan'"); - } - }//end if + //If its a 'textscan' (CSV) and the file name matches the expected type, then this is a datasource's connection we want to remap + //to point to a new CSV file + if ((XmlHelper.SafeParseXmlAttribute(xThisConnection, "class", "") == "textscan") && + (string.Compare(XmlHelper.SafeParseXmlAttribute(xThisConnection, "filename", ""), + oldDatasourceFilename, true) == 0)) + { + + //Find any relation nodes beneath the datasource + //Newer version of the document model put the textscan connection inside a federated data source + //to deal with that, we need to look upward from the connection and adjacent in the DOM to find the correct + //node to replace. This is done by looking at child nodes in the datasource + var xNodeAllConnectionRelations = xnodeDatasource.SelectNodes(".//relation"); + XmlNode xNodeRelation = null; + if (xNodeAllConnectionRelations != null) + { + if(xNodeAllConnectionRelations.Count == 1) + { + xNodeRelation = xNodeAllConnectionRelations[0]; + } + else + { + statusLog.AddError("CSV replacement. Expected 1 Relation in data source definition, actual " + xNodeAllConnectionRelations.Count.ToString()); + } + } + + + //Only if we have all the elements need to replace, should we go ahead with the replacement + if ((xNodeRelation != null) && (xThisConnection != null)) + { + //Point to the new directory/path + xThisConnection.Attributes["directory"].Value = newCsvDirectory; + xThisConnection.Attributes["filename"].Value = newCsvFileName; + + xNodeRelation.Attributes["name"].Value = newDatasourceRelationName; + xNodeRelation.Attributes["table"].Value = newDatasourceRelationTable; + + replaceItemCount++; + } + } + + }//end: foreach xThisConnection + } + }//end foreach }//end if diff --git a/TabRESTMigrate/_SampleFiles/ReadMe.txt b/TabRESTMigrate/_SampleFiles/ReadMe.txt new file mode 100644 index 0000000..1531269 --- /dev/null +++ b/TabRESTMigrate/_SampleFiles/ReadMe.txt @@ -0,0 +1,7 @@ +NOTE: +The Tableau Workbook in this directory (SiteInventory.twb) is used as a template from which the Site Inventory Workbookis generated. + +If you want to edit the Tableau Workbook, MAKE SURE you have it point to a sample *.csv file named 'siteInventoryTemplate.csv'. +The TabMigrate code that generates the Site Inventory workbook looks for and replaces the data file references to +'siteInventoryTemplate.csv' in this template workbook. If the Workbook is saved with a reference to another file name the search/replace +will not work and the result will be a Tableau Workbook that does NOT point to the data of the exported site. diff --git a/TabRESTMigrate/_SampleFiles/SiteInventory.twb b/TabRESTMigrate/_SampleFiles/SiteInventory.twb index 5649f56..614fc24 100644 --- a/TabRESTMigrate/_SampleFiles/SiteInventory.twb +++ b/TabRESTMigrate/_SampleFiles/SiteInventory.twb @@ -12,10 +12,10 @@ - + - + @@ -36,15 +36,10 @@ - - - - - - - - - + + + + @@ -430,118 +425,13 @@ "str" - - user-role - 129 - [user-role] - [siteInventory_2015-11-18-1640-03#csv] - user-role - 19 - string - Count - 1 - 1073741823 - true - - - "en_US" - "heap" - 4294967292 - 8 - "str" - - - - user-id - 129 - [user-id] - [siteInventory_2015-11-18-1640-03#csv] - user-id - 20 - string - Count - 1 - 1073741823 - true - - - "en_US" - "heap" - 4294967292 - 8 - "str" - - - - user-name - 129 - [user-name] - [siteInventory_2015-11-18-1640-03#csv] - user-name - 21 - string - Count - 1 - 1073741823 - true - - - "en_US" - "heap" - 4294967292 - 8 - "str" - - - - group-id - 129 - [group-id] - [siteInventory_2015-11-18-1640-03#csv] - group-id - 22 - string - Count - 1 - 1073741823 - true - - - "en_US" - "heap" - 4294967292 - 8 - "str" - - - - group-name - 129 - [group-name] - [siteInventory_2015-11-18-1640-03#csv] - group-name - 23 - string - Count - 1 - 1073741823 - true - - - "en_US" - "heap" - 4294967292 - 8 - "str" - - subscription-id 129 [subscription-id] [siteInventory_2015-11-18-1640-03#csv] subscription-id - 24 + 19 string Count 1 @@ -562,7 +452,7 @@ [subscription-name] [siteInventory_2015-11-18-1640-03#csv] subscription-name - 25 + 20 string Count 1 @@ -583,7 +473,7 @@ [subscription-type] [siteInventory_2015-11-18-1640-03#csv] subscription-type - 26 + 21 string Count 1 @@ -604,7 +494,7 @@ [view-id] [siteInventory_2015-11-18-1640-03#csv] view-id - 27 + 22 string Count 1 @@ -1406,128 +1296,115 @@ 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 - fRAmXavTZr33SQ8hPq0hAWjrGsS7Hmd06hkGOlw8eWT0pkcLXLy4wLlzU/vW35e/9JV9a0vo - S9OLYCGa6eEUweUM85t5Dt/huP9f/Pt/91CGIO6sc3CYC1//Q6LrM2RxMT+3xAufv0CLTr46 - ZecA1Ev8/OdvYrAo5GJVOnpcVFQTZkMdBShkUxhsHi6c/wy/ePUV8uUaTrcXq6GCxWxgfSuF - yWCk29fK9Oom2+t+cvkC7V43qr2LZ46PEpz58OHMVNyFBmhceu1H9LzwR1iLbxAtqPQ69VFT - 7RwAtUIinUcx5rGa+0jFYxTrZozVArkKmLUyBrvK2tIi6ZKKWVHIZ5IEt9bo6x/AZLdDucJm - eIt6XSMcWGUzkef44QH8fcce0hTFvaj5GKmSkerCdZyGFhLpPL3O/bkG7FG3cwDMdob7OjA6 - Jnjy+O1X71+8eJFz584BMDQ23pABisYzOjr4R//i33z029NNHcvDJkWw0LU9FcEL8/McOnz4 - jo/Nz89z+KPHisUigUDgk99v9tJLL93y+4kTJxjwe3n3w2Ve+PzzuinGRHPsKgAfvPMGeaMH - YzUPQHhjlcDmNsZymo7hwySCAfy+VuajFXy2Cj/+P3O0+twsrcQYH2olHArj9rnZXAnh6xvl - 7FNHWVtbu6WPwcFBTp48SdfCdZIl8Nv3ba5C3GZX76+WFhO/ee99YlthltY2qNc14vE4RkXl - 8gdXiCcy1KsFLA43sXiURCJJKlfBYTUSjQRJZAsk4ttoBivRcPCu/axff4efXw1gN6p7nqAQ - O2lgDVDh4sVpzp3buaj6xje+ccvvzz33HBcuXGjMkIT4FCmCha41/XLo6J9ebfYQdKHjD4+j - VfNcWwqTCi/Qd/g0w30dzR5W0zU8ABcvXgRqxEJhDj1xnNXV9VvOBJdX0o0egvjI5fevEonN - YLWfY37+KsN9LzR7SE3X8IOMocAqoUiUSjHNlStXmV9cIZdK4PbKu8/DlskkiCSqbCy9jdmi - jzO999L0GiD4b99sZve60ffH975Lmh41PQBCNJOcZxW6JgEQuiYBELomARC6JgEQuiYBELom - ARC6JgEQuiYBELomARC6JgEQuiYBELomARC6JgEQuiYBELomARC6JgEQuiYBELomARC6JgEQ - uiYBELomARC6JgEQuiYBELomARC6JgEQuiYBELomARC6JgEQuiYBELomARC6JgEQuiYBELom - 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 + R40f/+9Z/AEvC0tJxof8xKKxu4rglZWVO/oYHBzk2LFjdM1fJ12BoHPX5irEXXb0/mprsfDb + Ty6Q3IixsLJGo6GTSqUwKxoXL10mtZWjUS9hc3lJphJsbaXJFGq3iuCtfImt1OZtRfC9ha9/ + xM+urOI0a489QSG208QaoMYHH0zz8ssntt3qG9/4xh2/v/LKK7z66qvNGZIQnyNFsDA0KTGF + oUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJ + AIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCE + oUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJ + AIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCE + oUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEoUkAhKFJAIShSQCEod0KwEZ0 + lZnZeap17Z4blvMZ8qXaExuYEE/CrQD89Ec/IlNM85/+4m2ikTUW5kNUqyVmZ+eoqg2ufPJr + lmJJ8ukkCytr6EA6EWdxJcJv3v2f/PDdD2j8rq1sapPQ8ip6QyU0N0u+VCWxGWdhfpZipc73 + /uM3uRBaQ62WmJ2dp67pxGNrzM7OU62U+Oa//SbRjSTR8BKJdH5v/jLCECyf/agyPzdPd28/ + /+v7b3H2D7/GX37nv3Lo2HG+/b2/YqK9Tmkzyg+/+w5Bv43eA0dZWdvgzOnjlIoFNOfNVspb + a/y3//4ef+cLJ3nvk19g6T3Ej3/ybbwUmDx1knd+cYmWQgGA//If/h2ejiDvf3yDQuwaJ48d + 5uJsmEK+ANU0f/6t7/Mn//jrdLR6nvxfRhjCbTWAna/+o3/CP/jKF2nxtHNscgxdV1D0BgoK + HR2d6HoDVW3gae9lrL8DUND1Bq2tbWhqDbUBekOjAegNHa2hY1IUdF0Bi5MXTk6hlUq0tvqp + 1VRUVcVsdXL0+UM4ve1MHT5AudbA42xBNTn50tnjvPeTn+3V30YYgKLrug6wEY3S3tuLBYhG + o/T29qJWSywsrzHQ38M7P/g+w6f/iJE22EiXODA2QioeIVeD4d4O5ueXGZ2YwGqC1EaUdElj + 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 + oSX+4PVjRNfT9A0O8PGvfo6qt9CwmnA4PdSqdSx6mbW1JH/wx3+PSCTCxNFePvzV+1gdbqwO + N7nNVRomG119Qxwc/ezM0XZF8InTL/PeT96XAIhbnsgNc/KZFHOhJcwmuDazhMf52RfWVStl + 0pkc5UIeVa2ymcpSzOdRbA56gn50/WaQBnqD2F1+6uUC+XwBd2sAr89DKZ966HH853//b/D0 + DDVhhuJZ9dTUADu5Iryd+xXBU1NTj9my2I+emgDsFimCxU40dQlUL+eYnp5mYzNBsare9Xw8 + vg7A1uY658+fZytXvOP5XG7nX5kUi8VYWFhgYWEBgEJqleX17COMXhjBE/k4tM20yqUbIRxK + HbPNQUujgsUdoJRYZiUcYGTiKDZrnGIqyvyNHC0tOh5vK6GlML//xus76/NzRXBqdY5ZfYLh + bt9uT0/sA009Anz6cej0ZoxUoY7P58Ftt1AoFCgVS5TzGWbnQlTrN48O6c04i8thzCadmaUo + zl34p9/BgYHHbkPsX3tSAyyEQoyNjzel7duLYCl8xYPsuyK4Uqlgt9v3ehjiGbHvAiDETsg3 + RwhDkwAIQ5MACEOTAAhDkwAIQ5MACEOTAAhDkwAIQ5MACEOTAAhDkwAIQ5MACEOTAAhDkwAI + Q5MACEOTAAhDkwAIQ5MACEOTAAhDkwAIQ5MACEOTAAhDkwAIQ5MACEOTAAhDkwAIQ5MACEP7 + /xCMBLFTQzKkAAAAAElFTkSuQmCC iVBORw0KGgoAAAANSUhEUgAAAMAAAABACAYAAABMbHjfAAAACXBIWXMAABJ0AAASdAHeZh94