Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

feat(time-format): improve support for formatting with granularity in mind #509

Merged
merged 10 commits into from
May 22, 2020

Conversation

kristw
Copy link
Contributor

@kristw kristw commented May 19, 2020

🏆 Enhancements

What are the changes?

Extends core time formatting functions to include granularity support

  • getTimeFormatter(formatId) to getTimeFormatter(formatId?: string, granularity?: TimeGranularity)
  • formatTime(formatId, value) to formatTime(formatId: string | undefined, value: Date | null | undefined, granularity?: TimeGranularity)

New APIs

  • getTimeRangeFormatter(formatId?: string)
  • formatTimeRange(formatId: string | undefined, range: (Date | null | undefined)[])

Others

Convert the granularity string into constants.

Explanation of technical changes

When a time includes granularity, it means the given time is not a single point in time, but a time range. The changes in this PR includes logic for reconstructing the time range from time and granularity. The current logic assumes that the input time will be a valid output from database based on the granularity. For example, if the granularity is P1M (monthly), the database will always return the first day of the month.

Formatting a time point with granularity then follows these steps:

  1. Convert time and granularity into a time range.
  2. Pass the output time range to a TimeRangeFormatter, along with formatId (~d3 format string) or if no format is specified, look up default format string from a lookup table based on granularity (TimeFormatsForGranularity)
  3. The TimeRangeFormatter at the moment is quite naive. Just try to apply same formatting to the start and end of the time range. If both start and end have the same output then return just start, otherwise return ${start} - ${end}.

See more concrete examples in the unit tests.

Future plans

  • TimeRangeFormatter can also have its own registry in case developer wants to override how certain format works for range.
  • Same with the TimeFormatsForGranularity, which each application may have different opinion about default settings.

@kristw kristw requested a review from a team as a code owner May 19, 2020 18:20
@vercel
Copy link

vercel bot commented May 19, 2020

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/superset/superset-ui/q2ddcb6he
✅ Preview: https://superset-ui-git-kristw-time-format.superset.now.sh

@codecov
Copy link

codecov bot commented May 19, 2020

Codecov Report

Merging #509 into master will increase coverage by 0.68%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #509      +/-   ##
==========================================
+ Coverage   22.63%   23.32%   +0.68%     
==========================================
  Files         281      286       +5     
  Lines        6692     6752      +60     
  Branches      648      672      +24     
==========================================
+ Hits         1515     1575      +60     
  Misses       5138     5138              
  Partials       39       39              
Impacted Files Coverage Δ
...-time-format/src/factories/createMultiFormatter.ts 100.00% <ø> (ø)
...ckages/superset-ui-time-format/src/utils/d3Time.ts 100.00% <ø> (ø)
...et-ui-time-format/src/TimeFormatsForGranularity.ts 100.00% <100.00%> (ø)
...kages/superset-ui-time-format/src/TimeFormatter.ts 100.00% <100.00%> (ø)
...-time-format/src/TimeFormatterRegistrySingleton.ts 100.00% <100.00%> (ø)
.../superset-ui-time-format/src/TimeRangeFormatter.ts 100.00% <100.00%> (ø)
packages/superset-ui-time-format/src/types.ts 100.00% <100.00%> (ø)
...es/superset-ui-time-format/src/utils/createTime.ts 100.00% <100.00%> (ø)
...format/src/utils/createTimeRangeFromGranularity.ts 100.00% <100.00%> (ø)
...set-ui-time-format/src/utils/stringifyTimeInput.ts 100.00% <100.00%> (ø)
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4ceacfb...2150aa8. Read the comment docs.

@@ -1,5 +1,5 @@
import { utcFormat, timeFormat } from 'd3-time-format';
import { utcUtils, localTimeUtils } from '../utils';
import { utcUtils, localTimeUtils } from '../utils/d3Time';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move and rename file

/**
* search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
*/
export const TimeGranularity = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this also a constant to avoid having to type the awkward granularity string for WEEK_ENDING_xxx

Copy link
Contributor

@ktmud ktmud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, this is definitely a nice feature to have, but I'm worried whether it will lead to potential confusions if we do date truncating on both backend and frontend----users might see inconsistent datetime in charts and raw data.

What is the use case for the new API?

packages/superset-ui-time-format/src/types.ts Show resolved Hide resolved
packages/superset-ui-time-format/src/utils/createDate.ts Outdated Show resolved Hide resolved
}

// Year
return deductOneMs(createDate({ year: year + 1, useLocalTime }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How you thought of utilize d3-time intervals? https://github.com/d3/d3-time#interval_round

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could work. Will look into it.

Copy link
Contributor Author

@kristw kristw May 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried it. The floor and ceil functions work if the value is in the middle of the range but not so useful for the case we are handlings that the inputs are already at the beginning of the range. Will have to add offset before calling ceil. It also ceils to YYYY-mm-dd 00:00:00.000 instead of YYYY-mm-dd 23.59.59.999 that I would like.

@kristw
Copy link
Contributor Author

kristw commented May 19, 2020

In theory, this is definitely a nice feature to have, but I'm worried whether it will lead to potential confusions if we do date truncating on both backend and frontend----users might see inconsistent datetime in charts and raw data.

Could you elaborate on the truncation? I am not sure I understand what you mean.

@ktmud
Copy link
Contributor

ktmud commented May 19, 2020

In theory, this is definitely a nice feature to have, but I'm worried whether it will lead to potential confusions if we do date truncating on both backend and frontend----users might see inconsistent datetime in charts and raw data.

Could you elaborate on the truncation? I am not sure I understand what you mean.

computeEndTimeFromGranularity seems to be truncating the startTime based on granularity. I was worried since the backend already truncates timestamp based on time granularities, would there be a case when some piece of code passes the truncated timestamps to the frontend API with a different granularity?

@kristw
Copy link
Contributor Author

kristw commented May 20, 2020

computeEndTimeFromGranularity seems to be truncating the startTime based on granularity. I was worried since the backend already truncates timestamp based on time granularities, would there be a case when some piece of code passes the truncated timestamps to the frontend API with a different granularity?

computeEndTimeFromGranularity computes the endTime. I don't think it touches startTime at all. This is computing true time range for each data point.

For example, when a data point is returned from database, it seems like a single point in time.

{ time: 2010-04-01, value: 300 }

However, it is rarely a single point in time. If this query has MONTH granularity, the data point above actually means the entire April.

This is what the actual data point is.

{ time: [2010-04-01, 2010-04-30], value: 300 }

so computeEndTimeFromGranularity takes 2010-04-01 and MONTH and returns the end time 2010-04-30 just for formatting.

Now we know the time range is [2010-04-01, 2010-04-30], it depends on the format string.

  • If the format is %Y-%m this will be 2010-04 - 2010-04 which looks redundant, so we can merge and return 2010-04
  • If the format string is %Y-%m-%d this will be 2010-04-01 - 2010-04-30

@ktmud
Copy link
Contributor

ktmud commented May 20, 2020

computeEndTimeFromGranularity computes the endTime. I don't think it touches startTime at all. This is computing true time range for each data point.

I mean It calculates an endTime based on truncated startTime, since it only takes parts of startTime. I can see why this function can be useful, but would there be a case the truncating is done differently on the frontend and backend? For example, some db engine may round dates up instead of round-down? Or when backend rounded the dates to weeks, but the frontend still uses days because some chart forgot to pass the granularity to the formatter?

Maybe I was just overthinking.

@kristw
Copy link
Contributor Author

kristw commented May 20, 2020

For example, some db engine may round dates up instead of round-down?

The granularity WEEK_ENDING_xxx rounds date up and this function will react accordingly. I am not aware of other cases that it does not round down. (This would be an even broader issue that already exists if some database do round up by default.) The current code handles all granularity listed in the superset db_engine.

Or when backend rounded the dates to weeks, but the frontend still uses days because some chart forgot to pass the granularity to the formatter?

This is already the current behavior I think. This feature allows it to be more correct if the granularity is passed to the formatter. It is an opt-in feature and should not affect charts (except BigNumber) if we don't make any change to them.

@kristw kristw merged commit 2d8afa8 into master May 22, 2020
@delete-merged-branch delete-merged-branch bot deleted the kristw--time-format branch May 22, 2020 20:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants