turnilo icon indicating copy to clipboard operation
turnilo copied to clipboard

I have a few questions.

Open kimmy-wang opened this issue 4 years ago • 9 comments

version:1.26.0

  1. In the table view, select a time range of more than one week, and click on the W in the time shift, and an error will be reported. The message is as follows: Shifted period overlaps with main period. Why is it designed like this?
  2. How to set the start time of each week to Sunday?

kimmy-wang avatar Feb 23 '21 02:02 kimmy-wang

In the table view, select a time range of more than one week, and click on the W in the time shift, and an error will be reported. The message is as follows: Shifted period overlaps with main period. Why is it designed like this?

Because Turnilo can't calculate difference between periods if periods overlap.

How to set the start time of each week to Sunday?

There's no "week start" in Turnilo. All calculation on time buckets are done from selected date.

adrianmroz-allegro avatar Feb 23 '21 08:02 adrianmroz-allegro

@adrianmroz-allegro The second question, when you select a period of time and split by week, the start time of the week is Monday by default

kimmy-wang avatar Feb 23 '21 09:02 kimmy-wang

@adrianmroz-allegro The second question, when you select a period of time and split by week, the start time of the week is Monday by default

You're right! Sadly, plywood do not support origin parameter and we can't change it. Issue in plywood for reference: https://github.com/implydata/plywood/issues/172

We'd like to support this but even simpler contributions to plywood are stuck in limbo :(

adrianmroz-allegro avatar Feb 23 '21 09:02 adrianmroz-allegro

@adrianmroz-allegro We modify the value of origin by setting the requestDecorator attribute value in the config.yaml file, and finally modify the start date of the week.

exports.version = 1;

/**
 * object: 对象
 * path: 输入的路径
 * defaultVal: 默认值
 **/
function get(object, path, defaultVal = 'undefined') {
  // 先将path处理成统一格式
  var newPath = [];
  if (Array.isArray(path)) {
    newPath = path;
  } else {
    // 先将字符串中的'['、']'去除替换为'.',split分割成数组形式
    newPath = path.replace(/\[/g, '.').replace(/\]/g, '').split('.');
  }

  // 递归处理,返回最后结果
  return (
    newPath.reduce((o, k) => {
      return (o || {})[k];
    }, object) || defaultVal
  );
}

/**
 * https://github.com/metabase/metabase/issues/6047
 * 周的时间是从周日到周六
 * @param query
 * @returns {*}
 */
function addGranularityOrigin(query) {
  var externalGranularity = get(query, 'granularity');
  if (typeof externalGranularity === 'object' && externalGranularity.period === 'P1W') {
    query.granularity.origin = '1970-01-04T00Z';
    return query;
  }

  var internalGranularity = get(query, 'dimension.extractionFn.granularity');
  if (typeof internalGranularity === 'object' && internalGranularity.period === 'P1W') {
    query.dimension.extractionFn.granularity.origin = '1970-01-04T00Z';
    return query;
  }

  var dimensions = get(query, 'dimensions');
  if (Array.isArray(dimensions)) {
    dimensions = dimensions.map(function(dimension){
      var dimensionGranularity = get(dimension, 'extractionFn.granularity');
      if(typeof dimensionGranularity === 'object' && dimensionGranularity.period === 'P1W') {
        var newDimension = {...dimension}
        newDimension.extractionFn.granularity.origin = '1970-01-04T00Z';
        return newDimension
      }
      return dimension
    })
    query.dimensions = dimensions
    return query;
  }

  return query;
}

// logger - is just a collection of functions that you should use instead of console to have your logs included with the Swiv logs
// params - is an object with the following keys:
//   * options: the decoratorOptions part of the cluster object
//   * cluster: Cluster - the cluster object
exports.druidRequestDecoratorFactory = function (logger, params) {
  // decoratorRequest: DecoratorRequest - is an object that has the following keys:
  //   * method: string - the method that is used (POST or GET)
  //   * url: string -
  //   * query: Druid.Query -
  return function (decoratorRequest) {
    var query = decoratorRequest.query;

    query = addGranularityOrigin(query);

    // 可以二次修改传递给druid的参数
    var decoration = {
      headers: {},
      query: query,
    };

    // This can also be async if instead of a value of a promise is returned.
    return decoration;
  };
};

kimmy-wang avatar Feb 23 '21 09:02 kimmy-wang

Hey, interesting take on this. I don't think that we would add something like that to turnilo, though.

Maybe we should add some documentation/wiki how to add such decorator for interested users?

adrianmroz-allegro avatar Feb 24 '21 10:02 adrianmroz-allegro

@adrianmroz-allegro How to add custom header information when sending a request to Druid?

  1. I added a custom attribute userAgent 截屏2021-02-25 下午3 28 49

......
requestOptionsWithDecoration({
          query,
          context,
          options: {
            method: "POST",
            url: url + "/druid/v2/" + (queryType === 'sql' ? 'sql/' : '') + (context['pretty'] ? '?pretty': ''),
            body: JSON.stringify(query),
            headers: {
              "Content-type": "application/json",
              "User-Agent": JSON.stringify(userAgent)
            },
            timeout: timeout
          }
        })
.......
  1. src/server/utils/requester/requester.ts
...
function createDruidRequester(cluster: Cluster, requestDecorator: DruidRequestDecorator, userAgent: CustomUserAgent): PlywoodRequester<any> {
  const { host, protocol } = getHostAndProtocol(new URL(cluster.url));
  const timeout = cluster.getTimeout();
  return druidRequesterFactory({ host, timeout, requestDecorator, protocol, userAgent });
}
...
  1. src/server/config.ts
...
export const SETTINGS_MANAGER = new SettingsManager(settingsStore, {
  logger,
  userAgent: { uid: null, hash: null },
  verbose: VERBOSE,
  anchorPath,
  initialLoadTimeout: SERVER_SETTINGS.getPageMustLoadTimeout(),
});
...
  1. src/server/app.ts
...
attachRouter('/plywood', plywoodRouter(settingsGetter, SETTINGS_MANAGER));
...
  1. src/server/routes/plywood/plywood.ts
let ex: Expression = null;
    try {
      ex = Expression.fromJS(expression);
    } catch (e) {
      res.status(400).send({
        error: 'bad expression',
        message: e.message,
      });
      return;
    }

    let settings;
    try {
      settingManager.setUserAgents({ uid: 'UIDUID', hash: '' });
      settings = await getSettings();
    } catch (e) {
      res.status(400).send({ error: 'failed to get settings' });
      return;
    }

    const myDataCube = settings.getDataCube(dataCube);
    if (!myDataCube) {
      res.status(400).send({ error: 'unknown data cube' });
      return;
    }

    if (!myDataCube.executor) {
      res.status(400).send({ error: 'un queryable data cube' });
      return;
    }

    if (!checkAccess(myDataCube, req.headers)) {
      res.status(403).send({ error: 'access denied' });
      return null;
    }

    // "native" clusters are not defined, maybe they should be defined as some stub object
    if (myDataCube.cluster) {
      req.setTimeout(myDataCube.cluster.getTimeout(), null);
    }
    const maxQueries = myDataCube.getMaxQueries();
    try {
      const data = await myDataCube.executor(ex, { maxQueries, timezone: queryTimezone });
      const reply: any = {
        result: Dataset.isDataset(data) ? data.toJS() : data,
      };
      res.json(reply);
    } catch (error) {
      console.log('error:', error.message);
      if (error.hasOwnProperty('stack')) {
        console.log(error.stack);
      }
      res.status(500).send({
        error: 'could not compute',
        message: error.message,
      });
    }

kimmy-wang avatar Feb 25 '21 07:02 kimmy-wang

Druid request decorator takes only these props: https://github.com/implydata/plywood-druid-requester/blob/master/src/druidRequester.ts#L59...L63 It does not have access to any dynamic values which are under turnip control besides query. I don't think it's Turnilo responsibility to add something like that.

adrianmroz avatar Feb 25 '21 09:02 adrianmroz

@adrianmroz How to ignore case, when typing in the search box? 截屏2021-03-01 下午4 29 58

kimmy-wang avatar Mar 01 '21 08:03 kimmy-wang

You can't.

I think Discussions would be better suited for such questions. Keep issues for bug reports and feature requests.

adrianmroz avatar Mar 01 '21 08:03 adrianmroz