25. Custom Adapters

25.1. Managing Adapters

Adapters can be Enabled / Disabled in the Adapters Dialog available in the MQC application. By disabling unnecessary Tool Adapters the speed of importing huge data can be improved.

The allowed file extensions for the Data Sources depend on the enabled Adapters.

../_images/MQC_ConfigAdapters_Manage.png

Figure 25.1 Enabling / Disabling Tool and Custom Adapters in the Adapter Dialog

Note

Not all available adapters are shown in the dialog. To add a special adapter click on Add Adapter and then Special to add the needed adapter.

../_images/MQC_ConfigAdapters_AddSpecialAdapter.png

Custom Adapters, provided by MES, available as Open Source or developed yourself, can be added in this Dialog. On import a C# class file gets compiled while an IronPython script is executed and validated. If an error occurs, the import fails and the error messages are shown. A custom adapter source file (.cs or .py) can contain multiple custom adapters.

../_images/MQC_ConfigAdapters_CompileValidation.png

Figure 25.2 Compiling error messages on a failed import of a Custom Adapter

After a custom adapter file is imported it will be saved together with the current MQC Project as a library item or a dxp file. If the source file is changed it has to be manually reloaded in this dialog so that it is compiled again and the adapter is updated in the MQC Project.

Additionally to the functionality of managing the adapters, the Adapters Dialog provides a button to view the Execution Order of the Adapters, which is defined in the Adapters itself.

../_images/MQC_ConfigAdapters_ExecutionOrder.png

Figure 25.3 Execution Order of all enabled Adapters. The Adapters are executed in this order, by first checking if the file extension(s) match the file that is to be imported and then let the Adapter validate itself, if the provided file is to be handled by it.

With the Button “Test importing a Report File” a single data source file can be tested for import. If there are any errors while checking the Adapters and executing the correct Adapter, the error messages are shown to the user, else the data is shown in a result table.

25.2. Developing a Custom Adapter

An MQC Data Source Adapter has to inherit the MES.MQC.CoreExtension.DataSources.Adapter.CustomAdapter class.

A Custom Adapter can be either a .net C# class or a IronPython class.

The Tool Adapters and Example Adapters can be downloaded from the Adapters Dialog in MQC.

../_images/MQC_ConfigAdapters_Overview.png

Figure 25.4 Adapters Dialog in MQC: Download-Button of Adapter Examples on the right side.

When developing a new custom adapter, the following properties and methods have to be implemented:

Required Properties

  • DataSource: string

  • FileExtensions: List<string>

Optional Properties

  • Name: string (defaults to class name)

  • Description: string (defaults to empty)

  • Priority: integer (defaults to 100)

Required Methods

  • IsValid: bool

  • Read: List<DataEntry>

Optional Methods - GetHumanReadableFilePath: string

25.2.1. CustomAdapter

/// <summary>
///    The CustomAdapter class is the base for all MQC Adapters
/// </summary>
public abstract class CustomAdapter
{
    /// <summary>
    ///    Unique Name of the Adapter
    ///    Defaults to the ClassName, can be overridden with a
    ///    user defined Name
    /// </summary>
    public virtual string Name

    /// <summary>
    ///    Description of the Adapter that is visible in the
    ///    Adapter-Dialog as a Popover
    ///    Absolute links get transformed into HTML Link-Tags,
    ///    Linebreaks (\n) get transformed into HTML linebreaks (<br>)
    ///    HTML Tags are not allowed
    /// </summary>
    public virtual string Description

    /// <summary>
    ///    File Extensions of the Adapter
    ///    This Property has to be defined and has to have at least one
    ///    file extension
    ///    The Adapter is only used for FilePaths with the defined
    ///    file extensions. The IsValid method is not called unless
    ///    the file extension matches.
    /// </summary>
    public abstract List<string> FileExtensions

    /// <summary>
    ///    Priority of the Adapter
    ///    The execution order of all adapters depend on the defined
    ///    priorities. The higher the priority the earlier the adapter
    ///    is validated and executed.
    ///    The default priority for Tool Adapters is between 10-110,
    ///    Custom Adapters should define a priority of 200 or higher if
    ///    they should be executed before the tool adapters.
    ///    If two adapters have the same priority, the order is depending
    ///    on the Name of the adapter.
    /// </summary>
    public virtual int Priority

    /// <summary>
    ///    The default Data Source of the Adapter
    ///    This Property has to be defined and is taken as data source
    ///    name for all data imported using this adapter.
    ///    If a report file contains data from multiple data sources, set
    ///    this Property to a default name (e.g. "Unknown") and
    ///    use the DataSource Property of each DataEntry object to define
    ///    a dedicated Data Source per metric.
    /// </summary>
    public abstract string DataSource

    /// <summary>
    ///    IsValid has to be implemented by the Adapter class
    ///    The method is called when the file extensions match
    ///    If the file extension is unique this method can just return
    ///    true, else it should check if the file should be imported by
    ///    the adapter
    ///    If true is returned, the current adapter is executed and the
    ///    Read method is called. No other adapter is checked afterwards.
    /// </summary>
    protected abstract bool IsValid(string filePath,
                                    AdapterMessageBag messageBag)

    /// <summary>
    ///    Read has to be implemented by the Adapter class
    ///    This method is called when the file extensions match and
    ///    isValid returns true, no other adapter is called
    ///    The data of the file should be read and returned as a List
    ///    of DataEntry
    /// </summary>
    protected abstract List<DataEntry> Read(string filePath,
                                            AdapterMessageBag messageBag)

    /// <summary>
    ///    Number of Human Readable Files
    /// </summary>
    public virtual int HumanReadableFileCount

    /// <summary>
    ///    Human Readable File Extensions of the Adapter
    /// </summary>
    public virtual List<string> HumanReadableFileExtensions

    /// <summary>
    ///    Human Readable File Names of the Adapter
    /// </summary>
    public virtual List<string> HumanReadableFileNames

    /// <summary>
    ///    Start of Human Readable File Names of the Adapter
    /// </summary>
    public virtual List<string> HumanReadableStartFileNames

    /// <summary>
    ///    Get the human readable file path
    ///    Can be overridden with a user defined function
    /// </summary>
    protected virtual string GetHumanReadableFilePath(string filePath)

    /// <summary>
    ///    Get the human readable file paths
    ///    Can be overridden with a user defined function
    /// </summary>
    protected virtual List<string> GetHumanReadableFilePaths(string filePath)

    /// <summary>
    ///    This is a private function that combine the result of
    ///    GetHumanReadableFilePath and GetHumanReadableFilePaths.
    ///    If the result is empty then return human readable files based
    ///    on settings.
    /// </summary>
    private List<string> GetAllHumanReadableFilePaths(string filePath)

    /// <summary>
    ///    LoadXmlFile is a Utility method
    ///    It loads the filePath as an XDocument, which can be
    ///    traversed via XPath
    ///    The document is stored in a property and returned.
    ///    The reading of the file is therefore only done once.
    /// </summary>

    protected XDocument LoadXmlFile(string filePath)

    /// <summary>
    ///    LoadHtmlFile is a Utility method
    ///    It loads the filePath as an HtmlDocument (HtmlAgilityPack)
    ///    See https://html-agility-pack.net/ for documentation.
    ///    The document is stored in a property and returned.
    ///    The reading of the file is therefore only done once.
    /// </summary>
    protected HtmlDocument LoadHtmlFile(string filePath)

    /// <summary>
    ///    LoadExcelFile is a Utility method
    ///    It loads the filePath as an DataSet
    ///    The DataSet is stored in a property and returned.
    ///    The reading of the file is therefore only done once.
    /// </summary>
    protected DataSet LoadExcelFile(string filePath)

    /// <summary>
    ///    LoadCsvFile is a Utility method
    ///    It loads the filePath as an DataSet
    ///    The DataSet is stored in a property and returned.
    ///    The reading of the file is therefore only done once.
    /// </summary>
    protected DataSet LoadCsvFile(string filePath)

    /// <summary>
    ///    TransformStringToValue is a Utility method
    ///    Parse a string value to a double value in a
    ///    culture-independent (invariant) way.
    /// </summary>
    protected double? TransformStringToValue(string value)

25.2.2. AdapterMessageBag

public class AdapterMessageBag
{
    /// <summary>
    ///    ThrowError is a Utility method
    ///    Throws an Error message to be either ignored, displayed at
    ///    a notification (on data source import or refresh)
    ///    or shown in a validation dialog (if a report file is
    ///    imported as a test).
    ///    The import of the current filePath is aborted with an
    ///    internal exception
    /// </summary>
    public void ThrowError(string title, string description)

    /// <summary>
    ///    ThrowWarning is a Utility method
    ///    Throws an Warning message to be either ignored, displayed
    ///    at a notification (on data source import or refresh)
    ///    or shown in a validation dialog (if a report file is
    ///    imported as a test).
    ///    The import of the current filePath continues unimpeded by
    ///    the warning.
    /// </summary>
    public void ThrowWarning(string title, string description)

25.2.3. DataEntry

/// <summary>
///    The DataEntry class contains the data for one data item to be
///     imported
///    Each Adapter returns a List of with objects of this class on
///     execution with the Read method
/// </summary>
public class DataEntry
{
    /// <summary>
    ///     Constructor without parameters
    /// </summary>
    public DataEntry()
    {
    }

    /// <summary>
    ///     Constructor with artifactPath, artifactName, measureName,
    ///     variableName, value, dateTime and dataSource
    /// </summary>
    public DataEntry(string artifactPath,
                     string artifactName,
                     string measureName,
                     string variableName,
                     DateTime dateTime,
                     double? value,
                     string dataSource = null)

    /// <summary>
    ///     Date of the report
    /// </summary>
    public DateTime DateTime { get; set; }

    /// <summary>
    ///     Name of the Data Source
    ///     This property is optional and can remain null, in this case
    ///    the DataSource property of the CustomAdapter is used
    /// </summary>
    public string DataSource { get; set; } = null;

    /// <summary>
    ///     Path of the Artifact
    /// </summary>
    public string ArtifactPath { get; set; }

    /// <summary>
    ///     Name of the Artifact
    ///     This property is optional, the ArtifactName will be taken
    ///    from the Project Structure if available or derived from the
    ///    artifact path if not.
    /// </summary>
    public string ArtifactName { get; set; }

    /// <summary>
    ///     Name of the Measure
    /// </summary>
    public string MeasureName { get; set; }

    /// <summary>
    ///     Name of the Measurement
    /// </summary>
    public string MeasurementName { get; set; }

    /// <summary>
    ///     Name of the Variable
    /// </summary>
    public string VariableName { get; set; }

    /// <summary>
    ///     The Value
    ///     Has to be of the double type
    /// </summary>
    public double? Value { get; set; }
}