Trying the other solution [step 2: implementing and installing the adapter]
This should be pretty straight-forward. I'll have to do these steps:
- Implement the adapter
- Implement Initialize()
- Implement MakeSchemaChanges()
- Implement MakeDataChanges()
- Install the adapter on the TFS VPC
- Navigate to the warehouse web service and invoke the Run command (which will make the warehouse read and execute the adapter)
That should be it. So, let's have a look at how the adapter looks like, method by method:
- public void Initialize(IDatastore ds)
public void Initialize(IDataStore ds)
{
// Check the data store
m_dataStore = ds;
if (m_dataStore == null)
{
throw new Exception("Null data store");
}
// Connect to the TFS Server
String url = "ORCASBETA2_VSTS"; //or perhaps it should be http://orcasbeta2_tfsv:8080 ?
TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(url);
if (tfs == null)
{
throw new Exception("TF Server instance not obtained for TFS url:" + url);
}
}
- public SchemaChangesResult MakeSchemaChanges()
public SchemaChangesResult MakeSchemaChanges()
{
SchemaChangesResult result = SchemaChangesResult.NoChanges;
WarehouseConfig config = m_dataStore.GetWarehouseConfig();
Fact codeMetricsFact = new Fact();
//Set the name of the fact
codeMetricsFact.Name = "Code Metrics";
//Set friendly name. This will be used in the OLAP cube in SQL Enterprise editions-
codeMetricsFact.FriendlyName = "Code Metrics";
//Create the fields the Code Metrics fact will have
Field maintainabilityIndex = new Field();
maintainabilityIndex.Name = "Maintainability Index";
maintainabilityIndex.Type = "INT";
maintainabilityIndex.AggregationFunction = "sum";
Field cyclomaticComplexity = new Field();
cyclomaticComplexity.Name = "Cyclomatic Complexity";
cyclomaticComplexity.Type = "INT";
cyclomaticComplexity.AggregationFunction = "sum";
Field depthOfInheritance = new Field();
depthOfInheritance.Name = "Depth of Inheritance";
depthOfInheritance.Type = "INT";
depthOfInheritance.AggregationFunction = "sum";
Field classCoupling = new Field();
classCoupling.Name = "Class Coupling";
classCoupling.Type = "INT";
classCoupling.AggregationFunction = "sum";
Field linesOfCode = new Field();
linesOfCode.Name = "Lines of Code";
linesOfCode.Type = "INT";
linesOfCode.AggregationFunction = "sum";
//Add the fields to the newly created fact table.
codeMetricsFact.Fields.Add(maintainabilityIndex);
codeMetricsFact.Fields.Add(cyclomaticComplexity);
codeMetricsFact.Fields.Add(depthOfInheritance);
codeMetricsFact.Fields.Add(classCoupling);
codeMetricsFact.Fields.Add(linesOfCode);
//Create and add dimensionUse for Build to fact. This means that this fact can be sliced by Build.
DimensionUse dimUse = new DimensionUse();
dimUse.FriendlyUseName = "Build";
dimUse.UseName = "Build";
dimUse.DimensionName = "Build";
dimUse.IntermediateFactName = null; //I don't know what the hell this does, really...
codeMetricsFact.DimensionUses.Add(dimUse);
//Add the new fact to the config
config.Facts.Add(codeMetricsFact);
//Check if adapter is requested to stop before starting transaction.
if (m_stopRequested)
{
return SchemaChangesResult.StopRequested;
}
// Use transactions for safe operation
m_dataStore.BeginTransaction();
try
{
// This will add the new fact and new dimension to the warehouse.
// If user wants to add new fields or measures to existing dimensions or facts in
// warehouse, then use the IDataStore.Add* methods. IDataStore.Add(config) will not
// work in that case.
// Also note that the Add method only appends the new facts and dimensions from the
// modified config. The current existing facts and dimensions will not get added twice.
m_dataStore.Add(config);
m_dataStore.CommitTransaction();
}
catch
{
m_dataStore.RollbackTransaction();
throw new Exception("Transaction failed. Could not make schema changes!");
}
result = SchemaChangesResult.ChangesComplete;
return result;
}
- public DataChangesResult MakeDataChanges()
public DataChangesResult MakeDataChanges()
{
DataChangesResult result = DataChangesResult.NoChanges;
FactEntry metricsDataEntry = m_dataStore.CreateFactEntry("Code Metrics");
string connString = "Data Source=ORCASBETA2_TFSV;" +
"Database=CMDB;" +
"Integrated Security=true;";
SqlConnection conn = new SqlConnection(connString);
try
{
conn.Open();
}
catch (Exception e)
{
m_dataStore.LogEvent(AdapterEventLevel.Error, "Could not open CMDB.");
return DataChangesResult.NoChanges;
}
try
{
SqlCommand command = new SqlCommand("HarvestCodeMetrics", conn);
command.CommandType = CommandType.StoredProcedure;
using (SqlDataReader myReader = command.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
// Harvest data from CMDB. The indexes of metricsDataEntry must correspond
// to an existing field in the Code Metrics fact table, as created in
// MakeSchemaChanges() above.
while (myReader.Read())
{
metricsDataEntry["Maintainability Index"] = myReader.GetInt32(myReader.GetOrdinal("MaintainabilityIndex"));
metricsDataEntry["Cyclomatic Complexity"] = myReader.GetInt32(myReader.GetOrdinal("CyclomaticComplexity"));
metricsDataEntry["Depth of Inheritance"] = myReader.GetInt32(myReader.GetOrdinal("DepthOfInheritance"));
metricsDataEntry["Class Coupling"] = myReader.GetInt32(myReader.GetOrdinal("ClassCoupling"));
metricsDataEntry["Lines of Code"] = myReader.GetInt32(myReader.GetOrdinal("LinesOfCode"));
metricsDataEntry["Build"] = myReader.GetString(myReader.GetOrdinal("Build"));
}
myReader.Close();
}
}
catch (Exception e)
{
m_dataStore.LogEvent(AdapterEventLevel.Error, "Could not read from CMDB");
return DataChangesResult.NoChanges;
}
//Check if adapter is requested to stop
if (m_stopRequested)
{
return DataChangesResult.StopRequested;
}
// Not requested to stop? Great, start the transaction then.
m_dataStore.BeginTransaction();
try
{
m_dataStore.SaveFactEntry(metricsDataEntry, false); // If true, the save may result in updating an _existing_ row.
m_dataStore.CommitTransaction();
}
catch
{
m_dataStore.RollbackTransaction();
throw new Exception("Error! Couldn't save the fact entry");
}
//we're done, mark that we've done some changes.
result = DataChangesResult.ChangesComplete;
m_dataStore.LogEvent(AdapterEventLevel.Informational, "Updated warehouse");
return result;
}
That's it. Let's try to build the adapter and install it, following these instructions. I'll publish a new post when I've tried this out.