Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/solr/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Google apps script Google Data Studio社区连接器getData()未按预期工作_Google Apps Script_Google Data Studio - Fatal编程技术网

Google apps script Google Data Studio社区连接器getData()未按预期工作

Google apps script Google Data Studio社区连接器getData()未按预期工作,google-apps-script,google-data-studio,Google Apps Script,Google Data Studio,上面是我的getData()函数 我的isAdminUser()返回true 当我试图可视化我的数据时,我得到以下错误 数据集配置错误 Data Studio无法连接到您的数据集。 从社区连接器请求数据时出错。如果此问题仍然存在,请向此社区连接器的提供商报告此问题。 错误ID:3d11b88b 每次我刷新数据时,错误代码都会更改,并且我找不到任何字典将错误id映射到错误 我尝试通过记录请求参数、响应.getContentText()和resData变量进行调试,以确保我的数据格式正确 以下是打

上面是我的getData()函数

我的isAdminUser()返回true

当我试图可视化我的数据时,我得到以下错误

数据集配置错误

Data Studio无法连接到您的数据集。

从社区连接器请求数据时出错。如果此问题仍然存在,请向此社区连接器的提供商报告此问题。

错误ID:3d11b88b

每次我刷新数据时,错误代码都会更改,并且我找不到任何字典将错误id映射到错误

我尝试通过记录请求参数、响应.getContentText()resData变量进行调试,以确保我的数据格式正确

以下是打印在堆栈驱动程序日志中的日志

请求

{configParams={/Personal-config-data/},字段=[{name=LASTNAME}]}

响应。getContentText()

{“schema”:[{“name”:“LASTNAME”,“dataType”:“STRING”}],“rows”:[{“values”:[“test”]},{“values”:[“Dummy”]},{“values”:[“One”]},{“values”:[“Nagargoje”]},{“values”:[“]},{“values”:[”“],{“values”:[”“],{“values”:[”“],{“values”:[”“],{“values”:[”“],{“values”],{“filterApplied]:false}

resData

{rows=[{values=[test]},{values=[test]},{values=[Dummy]}, {values=[One]},{values=[Nagargoje]},{values=[]},{values=[]}, {values=[]},{values=[]},{values=[]}],filtersApplied=false, schema=[{name=LASTNAME,dataType=STRING}]}

我不确定getData()函数有什么问题


我返回的对象似乎与这里给出的结构匹配

getData
应该只返回请求字段的数据。在
request.fields中
应该有所有请求字段的列表。仅限制这些字段的数据,然后将解析后的数据发送回

首先:您可以随时查看其他人为自定义Google Data Studio连接器所做的操作。他们是一个很好的信息来源。有关更多信息,请查看上的文档

第二:我的实施是为了一个时间跟踪系统,因此具有机密的GDPR相关数据。这就是为什么我不能只给你回复信息。但是我汇编了这个代码。它包含authenticonfiction、HTTP GET数据获取和数据转换。代码下面有解释。如果需要进一步帮助,请再次签出开源连接器

function getData(request){
  try{  
  var options = {
  'method' : 'post',
  'contentType': 'application/json',
  'payload' : JSON.stringify(request)
  };
  response=UrlFetchApp.fetch(getDataUrl, options);

  resData = JSON.parse(response.getContentText())

  return resData

  }catch (e) { 
    e = (typeof e === 'string') ? new Error(e) : e;
    Logger.log("Catch", e);
    throw e;
  }
}
一个示例请求,用于筛选名称以J开头的所有用户

var cc = DataStudioApp.createCommunityConnector();

const URL_DATA = 'https://www.myverysecretdomain.com/api';
const URL_PING = 'https://www.myverysecretdomain.com/ping';
const AUTH_USER = 'auth.user'
const AUTH_KEY = 'auth.key';
const JSON_TAG = 'user';

String.prototype.format = function() {
  // https://coderwall.com/p/flonoa/simple-string-format-in-javascript
  a = this;
  for (k in arguments) {
    a = a.replace("{" + k + "}", arguments[k])
  }
  return a
}

function httpGet(user, token, url, params) {
  try {
    // this depends on the URL you are connecting to
    var headers = {
      'ApiUser': user,
      'ApiToken': token,
      'User-Agent': 'my super freaky Google Data Studio connector'
    };

    var options = {
      headers: headers
    };

    if (params && Object.keys(params).length > 0) {
      var params_ = [];
      for (const [key, value] of Object.entries(params)) {
        var value_ = value;
        if (Array.isArray(value))
          value_ = value.join(',');

        params_.push('{0}={1}'.format(key, encodeURIComponent(value_)))
      }

      var query = params_.join('&');
      url = '{0}?{1}'.format(url, query);
    }

    var response = UrlFetchApp.fetch(url, options);

    return {
      code: response.getResponseCode(),
      json: JSON.parse(response.getContentText())
    }  
  } catch (e) {
    throwConnectorError(e);
  }
}

function getCredentials() {
  var userProperties = PropertiesService.getUserProperties();
  return {
    username: userProperties.getProperty(AUTH_USER),
    token: userProperties.getProperty(AUTH_KEY)
  }
}

function validateCredentials(user, token) {
  if (!user || !token) 
    return false;

  var response = httpGet(user, token, URL_PING);

  if (response.code == 200)
    console.log('API key for the user %s successfully validated', user);
  else
    console.error('API key for the user %s is invalid. Code: %s', user, response.code);

  return response;
}  

function getAuthType() {
  var cc = DataStudioApp.createCommunityConnector();
  return cc.newAuthTypeResponse()
    .setAuthType(cc.AuthType.USER_TOKEN)
    .setHelpUrl('https://www.myverysecretdomain.com/index.html#authentication')
    .build();
}

function resetAuth() {
  var userProperties = PropertiesService.getUserProperties();
  userProperties.deleteProperty(AUTH_USER);
  userProperties.deleteProperty(AUTH_KEY);

  console.info('Credentials have been reset.');
}

function isAuthValid() {
  var credentials = getCredentials()
  if (credentials == null) {
    console.info('No credentials found.');
    return false;
  }

  var response = validateCredentials(credentials.username, credentials.token);
  return (response != null && response.code == 200);
}

function setCredentials(request) {
  var credentials = request.userToken;
  var response = validateCredentials(credentials.username, credentials.token);

  if (response == null || response.code != 200) return { errorCode: 'INVALID_CREDENTIALS' };

  var userProperties = PropertiesService.getUserProperties();
  userProperties.setProperty(AUTH_USER, credentials.username);
  userProperties.setProperty(AUTH_KEY, credentials.token);

  console.info('Credentials have been stored');

  return {
    errorCode: 'NONE'
  };
}

function throwConnectorError(text) {
  DataStudioApp.createCommunityConnector()
    .newUserError()
    .setDebugText(text)
    .setText(text)
    .throwException();
}

function getConfig(request) {
  // ToDo: handle request.languageCode for different languages being displayed
  console.log(request)

  var params = request.configParams;
  var config = cc.getConfig();

  // ToDo: add your config if necessary

  config.setDateRangeRequired(true);
  return config.build();
}

function getDimensions() {
  var types = cc.FieldType;

  return [
    {
      id:'id',
      name:'ID',
      type:types.NUMBER
    },
    {
      id:'name',
      name:'Name',
      isDefault:true,
      type:types.TEXT
    },
    {
      id:'email',
      name:'Email',
      type:types.TEXT
    }
  ];
}

function getMetrics() {
  return [];
}

function getFields(request) {
  Logger.log(request)

  var fields = cc.getFields();

  var dimensions = this.getDimensions();
  var metrics = this.getMetrics();
  dimensions.forEach(dimension => fields.newDimension().setId(dimension.id).setName(dimension.name).setType(dimension.type));  
  metrics.forEach(metric => fields.newMetric().setId(metric.id).setName(metric.name).setType(metric.type).setAggregation(metric.aggregations));

  var defaultDimension = dimensions.find(field => field.hasOwnProperty('isDefault') && field.isDefault == true);
  var defaultMetric = metrics.find(field => field.hasOwnProperty('isDefault') && field.isDefault == true);

  if (defaultDimension)
    fields.setDefaultDimension(defaultDimension.id);
  if (defaultMetric)
    fields.setDefaultMetric(defaultMetric.id);

  return fields;
}

function getSchema(request) {
  var fields = getFields(request).build();
  return { schema: fields };
}

function convertValue(value, id) {  
  // ToDo: add special conversion if necessary
  switch(id) {      
    default:
      // value will be converted automatically
      return value[id];
  }
}

function entriesToDicts(schema, data, converter, tag) {

  return data.map(function(element) {

    var entry = element[tag];
    var row = {};    
    schema.forEach(function(field) {

      // field has same name in connector and original data source
      var id = field.id;
      var value = converter(entry, id);

      // use UI field ID
      row[field.id] = value;
    });

    return row;
  });
}

function dictsToRows(requestedFields, rows) {
  return rows.reduce((result, row) => ([...result, {'values': requestedFields.reduce((values, field) => ([...values, row[field]]), [])}]), []);
}

function getParams (request) { 
  var schema = this.getSchema();
  var params;

  if (request) {
    params = {};

    // ToDo: handle pagination={startRow=1.0, rowCount=100.0}
  } else {
    // preview only
    params = {
      limit: 20
    }
  }

  return params;
}

function getData(request) {
  Logger.log(request)

  var credentials = getCredentials()
  var schema = getSchema();
  var params = getParams(request);

  var requestedFields;  // fields structured as I want them (see above)
  var requestedSchema;  // fields structured as Google expects them
  if (request) {
    // make sure the ordering of the requested fields is kept correct in the resulting data
    requestedFields = request.fields.filter(field => !field.forFilterOnly).map(field => field.name);
    requestedSchema = getFields(request).forIds(requestedFields);
  } else {
    // use all fields from schema
    requestedFields = schema.map(field => field.id);
    requestedSchema = api.getFields(request);
  }

  var filterPresent = request && request.dimensionsFilters;
  //var filter = ...
  if (filterPresent) {
    // ToDo: apply request filters on API level (before the API call) to minimize data retrieval from API (number of rows) and increase speed
    // see https://developers.google.com/datastudio/connector/filters

    // filter = ...   // initialize filter
    // filter.preFilter(params);  // low-level API filtering if possible
  }

  // get HTTP response; e.g. check for HTTT RETURN CODE on response.code if necessary
  var response = httpGet(credentials.username, credentials.token, URL_DATA, params);  

  // get JSON data from HTTP response
  var data = response.json;

  // convert the full dataset including all fields (the full schema). non-requested fields will be filtered later on  
  var rows = entriesToDicts(schema, data, convertValue, JSON_TAG);

  // match rows against filter (high-level filtering)
  //if (filter)
  //  rows = rows.filter(row => filter.match(row) == true);

  // remove non-requested fields
  var result = dictsToRows(requestedFields, rows);

  console.log('{0} rows received'.format(result.length));
  //console.log(result);

  return {
    schema: requestedSchema.build(),
    rows: result,
    filtersApplied: filter ? true : false
  };
}
HTTP GET返回的JSON数据包含所有字段(完整模式)

一旦数据被过滤和转换/转换,您就会得到这个结果,Google data Studio会完美地显示这个结果:

[ { user: 
     { id: 1,
       name: 'Jane Doe',
       email: 'jane@doe.com' } },
  { user: 
     { id: 2,
       name: 'John Doe', 
       email: 'john@doe.com' } }
]

因此我的getData()函数没有问题,问题存在于清单文件中。 我正在搜索关于通过URL传递参数的信息,偶然发现了一个名为 dataStudio.useQueryConfig并将其添加到我的清单文件中,并将其值设置为true。 Google Data studio希望我返回
getData()
的查询配置。 但我真正想要的是

不管怎样,我能够调试它,多亏了你建议我看一看

我实现了一个运行良好的接口,所以我将它返回的内容记录在getData()中,并在代码中使用该格式/结构,但我的连接器仍然无法工作

我的下一个假设是,我的getSchema()返回值可能有问题。所以我也记录了这一点,然后复制粘贴了getData()和getSchema()返回变量的硬编码值

即使这样也不行,所以我最后一次打赌清单文件一定有问题,也许我在其中添加的虚拟链接就是问题所在。然后,在进行了实地对比之后,我终于能够让我的社区连接器工作了


如果错误消息有点帮助,并且看起来不那么普通,那么调试就会更容易。

这正是我正在做的,我有30多个字段,但是
请求。字段
仅请求
LASTNAME
。所以我只是发送字段
LASTNAME
的值,正如您在resData中看到的那样。我还缺什么吗?
[ { user: 
     { id: 1,
       name: 'Jane Doe',
       email: 'jane@doe.com' } },
  { user: 
     { id: 2,
       name: 'John Doe', 
       email: 'john@doe.com' } }
]
{
    filtersApplied=true, 
    schema=[
        {
            isDefault=true, 
            semantics={
                semanticType=TEXT, 
                conceptType=DIMENSION
            }, 
            label=Name, 
            name=name, 
            dataType=STRING
        }
    ], 
    rows=[
        {values=[Jane Doe]}, 
        {values=[John Doe]}
    ]
}