<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: Data format expected by notebook timeseries visualizations in Developer Q&amp;A Forum</title>
    <link>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211589#M319</link>
    <description>&lt;P&gt;Hi Scott,&lt;/P&gt;
&lt;P&gt;with the dataframe example you were already quite close. In general, the visualizations work when you return the same data structure like the Query API (see type &lt;A href="https://developer.dynatrace.com/reference/sdks/client-query/#queryresult" target="_self"&gt;QueryResult&lt;/A&gt;). The two properties&amp;nbsp;&lt;CODE&gt;records&lt;/CODE&gt; and &lt;CODE&gt;types&lt;/CODE&gt; are relevant for a correct visualization.&lt;/P&gt;
&lt;P&gt;I tried to come up with a generic code snippet that should work with different metrics, dimensions and resolutions. Please let me know if this snippet produces the expected visualization.&lt;/P&gt;
&lt;PRE&gt;import { metricsClient } from '@dynatrace-sdk/client-classic-environment-v2';

export default async function () {
  const response = await metricsClient.query({
    acceptType: 'application/json; charset=utf-8', 
    from: 'now-7d',
    resolution: 'h',
    metricSelector: 'builtin:synthetic.http.availability.location.totalWoMaintenanceWindow'
  });
  const metrics = response.result;

  const records = convertMetricsToRecords(metrics);

  // extract dimensions from metric response to attach on returned type mapping
  const dimensionKeys = collectDimensionKeys(metrics);
  const dimensionTypes = dimensionKeys.reduce((result, dimension) =&amp;gt; ({
    ...result,
    [dimension]: {
      type: "string"
    }
  }), {});
  
  return {
    records,
    types: [{
      mappings: {
        values: {
          type: "array",
          types: [{
            mappings: {
              element: {
                type: "double"
              }
            }
          }]
        },
        ...dimensionTypes,
        timeframe: {
          type: "timeframe"
        },
        interval: {
          type: "duration"
        }
      },
      indexRange: [0, records.length]
    }],
  };
}

const collectDimensionKeys = (metrics) =&amp;gt; {
  const dimensionKeys = metrics
    .flatMap(metric =&amp;gt; metric.data.flatMap(data =&amp;gt; Object.keys(data.dimensionMap)));
  // ensure dimension keys are unique
  return Array.from(new Set(dimensionKeys));
}

const convertMetricDataToRecords = (metricData) =&amp;gt;
    metricData.map(data =&amp;gt; ({
      values: data.values,
      timeframe: {
        start: new Date(data.timestamps[0]).toISOString(),
        end: new Date(data.timestamps[data.timestamps.length - 1]).toISOString(),
      },
      // expects to have at least 2 datapoints
      interval: String((data.timestamps[1] - data.timestamps[0]) * 1000 * 1000),
      // add all dimension values as record fields
      ...data.dimensionMap,
    }));

const convertMetricsToRecords = (metrics) =&amp;gt;  
  metrics.flatMap(metric =&amp;gt; convertMetricDataToRecords(metric.data));
&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Mon, 08 May 2023 08:38:23 GMT</pubDate>
    <dc:creator>stefan_eggersto</dc:creator>
    <dc:date>2023-05-08T08:38:23Z</dc:date>
    <item>
      <title>Data format expected by notebook timeseries visualizations</title>
      <link>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211545#M317</link>
      <description>&lt;P&gt;My company is still&amp;nbsp; in preview mode of the new Dynatrace platform and UI.&amp;nbsp; I am trying to get familiar with using notebooks and dashboards.&amp;nbsp; The data I want to display, availability metrics for synthetic monitors, is not yet in Grail so I am using the classic SDK and the metricClient to get the metric data.&amp;nbsp; What is the expected format for the data, for say a line graph,&amp;nbsp; so that I can plot multiple monitors availability over time?&amp;nbsp; An example would be most helpful.&lt;/P&gt;</description>
      <pubDate>Fri, 19 May 2023 10:24:26 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211545#M317</guid>
      <dc:creator>scott-smith</dc:creator>
      <dc:date>2023-05-19T10:24:26Z</dc:date>
    </item>
    <item>
      <title>Re: Data format expected by notebook timeseries visualizations</title>
      <link>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211549#M318</link>
      <description>&lt;P&gt;For context, here is the JS code that I was using to attempt to format correctly.&amp;nbsp; Comments in code as to what kind of worked and what did not.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="markup"&gt;import { metricsClient } from '@dynatrace-sdk/client-classic-environment-v2';
import type { Timeseries } from '@dynatrace/strato-components-preview/charts';

// Entry point for the notebook
export default async function () {
  //'SMARTS_DOWN'
  //'SMARTS_DEGRADED'
  //HTTP_CHECK-C6E2037C672EC3AF
  //HTTP_CHECK-D1D93CAAAC8D8C85
  //HTTP_CHECK-301A4A1D1D711417
  
  //const metrics = await getMetricsByTags('SMARTS_DOWN');  
  const metrics = await getMetricsByMonitorId('HTTP_CHECK-C6E2037C672EC3AF');
  //console.log(metrics);

  // This works for a single monitor dataset
  return formatAsDataFrame(metrics);

  // Saw this format in docs: https://developer.dynatrace.com/reference/design-system/preview/charts/TimeseriesChart/
  // but does not appear to work for most visualizations
  //return formatAsTimeseriesArray(metrics);

  // Just a variation on the one above, returning as a dataFrame with typings
  // does not work for most visualizations
  //return formatAsTimeseriesDataFrame(metrics);
}

function formatAsTimeseriesDataFrame(metrics){
  let timeSeriesArray = formatAsTimeseriesArray(metrics);
  //console.log(timeSeriesArray);
  
    const types = [{
      mappings: {
        availability: { 
          type: 'array',
          types:[{
            mappings: {
              name: {type: 'string'},
              unit: {type: 'string'},
              datapoints: {
                type: 'array',
                types:[{
                  mappings: {
                    start: {type: 'datetime'},
                    end: {type: 'datetime'},
                    value: {type: 'double'}
                  }
                }]
              }
            }
            }]          
          },
        },
        indexRange: [
        0,
        timeSeriesArray.length -1
      ]
  }]

  // Create a new empty data frame for the result
  const dataFrame = {
    availability: timeSeriesArray,
    types: types
  }

  console.log(dataFrame);
  return dataFrame;
}
  
function formatAsTimeseriesArray(metrics){
  const availabilityMetrics = [];

  for(let i=0; i&amp;lt; metrics[0].data.length; i++) {
    //console.log(metrics[0].data[i].dimensions[0]);
    let timeseries = {
      name: metrics[0].data[i].dimensions[0],
      unit: 'percent',
      datapoints: []
    };

     for(let x=0; x&amp;lt; metrics[0].data[i].timestamps.length; x++){
        if(metrics[0].data[i].values[x] !== null){
          const dp = {
              start: metrics[0].data[i].timestamps[x],
              end: metrics[0].data[i].timestamps[x],
              value: metrics[0].data[i].values[x]
          };
          timeseries.datapoints.push(dp);
        }                       
    }

    availabilityMetrics.push(timeseries);   
  }  

  //console.log(availabilityMetrics);
  return availabilityMetrics;
}

function formatAsDataFrame(metrics){
  const types = [{
      mappings: {
        timeframe: { type: 'timeframe' },
        value: {type: 'double'},
        "entity.id": {type: 'string'}
        },
        indexRange: [
        0,
        metrics[0].data[0].timestamps.length - 1
      ]
  }]

  // Create a new empty data frame for the result
  const dataFrame = {
    records: [],
    types: types
  }

  for(let i=0; i&amp;lt; metrics[0].data.length; i++) {
    for(let x=0; x&amp;lt; metrics[0].data[i].timestamps.length; x++)
    {
      if(metrics[0].data[i].values[x] !== null){
        const record = {
          "entity.id": metrics[0].data[i].dimensions[0],
          timeframe: {
              start: metrics[0].data[i].timestamps[x],
              end: metrics[0].data[i].timestamps[x]
            },
          value: metrics[0].data[i].values[x]        
        }
        dataFrame.records.push(record); 
      }                    
    }    
  }

  //console.log(dataFrame);
  return dataFrame;
}

//get a single HTTP monitor's metrics
async function getMetricsByMonitorId(monitorId) {
  //HTTP_CHECK-301A4A1D1D711417 
  const response = await metricsClient.query({
    acceptType: 'application/json; charset=utf-8', 
    entitySelector: 'entityId('+ monitorId +')',
    from: 'now-3d',
    resolution: 'h',
    metricSelector: 'builtin:synthetic.http.availability.location.totalWoMaintenanceWindow'
  });
  
  return response.result;
}

//get multiple HTTP monitor metrics by tag and type
async function getMetricsByTags(availabilityTagValue) {
  //console.log(availabilityTagValue);
  
  const entitySel = 'type(dt.entity.http_check),tag(SENTRY_AVAILABILITY:' + availabilityTagValue + ')';
  const response = await metricsClient.query({
    acceptType: 'application/json; charset=utf-8', 
    entitySelector: entitySel,
    from: 'now-7d',
    resolution: 'h',
    metricSelector: 'builtin:synthetic.http.availability.location.totalWoMaintenanceWindow'
  });

  return response.result;
}&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Tue, 16 May 2023 12:42:02 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211549#M318</guid>
      <dc:creator>scott-smith</dc:creator>
      <dc:date>2023-05-16T12:42:02Z</dc:date>
    </item>
    <item>
      <title>Re: Data format expected by notebook timeseries visualizations</title>
      <link>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211589#M319</link>
      <description>&lt;P&gt;Hi Scott,&lt;/P&gt;
&lt;P&gt;with the dataframe example you were already quite close. In general, the visualizations work when you return the same data structure like the Query API (see type &lt;A href="https://developer.dynatrace.com/reference/sdks/client-query/#queryresult" target="_self"&gt;QueryResult&lt;/A&gt;). The two properties&amp;nbsp;&lt;CODE&gt;records&lt;/CODE&gt; and &lt;CODE&gt;types&lt;/CODE&gt; are relevant for a correct visualization.&lt;/P&gt;
&lt;P&gt;I tried to come up with a generic code snippet that should work with different metrics, dimensions and resolutions. Please let me know if this snippet produces the expected visualization.&lt;/P&gt;
&lt;PRE&gt;import { metricsClient } from '@dynatrace-sdk/client-classic-environment-v2';

export default async function () {
  const response = await metricsClient.query({
    acceptType: 'application/json; charset=utf-8', 
    from: 'now-7d',
    resolution: 'h',
    metricSelector: 'builtin:synthetic.http.availability.location.totalWoMaintenanceWindow'
  });
  const metrics = response.result;

  const records = convertMetricsToRecords(metrics);

  // extract dimensions from metric response to attach on returned type mapping
  const dimensionKeys = collectDimensionKeys(metrics);
  const dimensionTypes = dimensionKeys.reduce((result, dimension) =&amp;gt; ({
    ...result,
    [dimension]: {
      type: "string"
    }
  }), {});
  
  return {
    records,
    types: [{
      mappings: {
        values: {
          type: "array",
          types: [{
            mappings: {
              element: {
                type: "double"
              }
            }
          }]
        },
        ...dimensionTypes,
        timeframe: {
          type: "timeframe"
        },
        interval: {
          type: "duration"
        }
      },
      indexRange: [0, records.length]
    }],
  };
}

const collectDimensionKeys = (metrics) =&amp;gt; {
  const dimensionKeys = metrics
    .flatMap(metric =&amp;gt; metric.data.flatMap(data =&amp;gt; Object.keys(data.dimensionMap)));
  // ensure dimension keys are unique
  return Array.from(new Set(dimensionKeys));
}

const convertMetricDataToRecords = (metricData) =&amp;gt;
    metricData.map(data =&amp;gt; ({
      values: data.values,
      timeframe: {
        start: new Date(data.timestamps[0]).toISOString(),
        end: new Date(data.timestamps[data.timestamps.length - 1]).toISOString(),
      },
      // expects to have at least 2 datapoints
      interval: String((data.timestamps[1] - data.timestamps[0]) * 1000 * 1000),
      // add all dimension values as record fields
      ...data.dimensionMap,
    }));

const convertMetricsToRecords = (metrics) =&amp;gt;  
  metrics.flatMap(metric =&amp;gt; convertMetricDataToRecords(metric.data));
&lt;/PRE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 08 May 2023 08:38:23 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211589#M319</guid>
      <dc:creator>stefan_eggersto</dc:creator>
      <dc:date>2023-05-08T08:38:23Z</dc:date>
    </item>
    <item>
      <title>Re: Data format expected by notebook timeseries visualizations</title>
      <link>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211729#M325</link>
      <description>&lt;P&gt;This does appear to work.&amp;nbsp; I need to dig into it today, but thank you!&lt;/P&gt;</description>
      <pubDate>Tue, 09 May 2023 13:41:09 GMT</pubDate>
      <guid>https://community.dynatrace.com/t5/Developer-Q-A-Forum/Data-format-expected-by-notebook-timeseries-visualizations/m-p/211729#M325</guid>
      <dc:creator>scott-smith</dc:creator>
      <dc:date>2023-05-09T13:41:09Z</dc:date>
    </item>
  </channel>
</rss>

