superset icon indicating copy to clipboard operation
superset copied to clipboard

An error occurred while rendering the visualization: TypeError: o.getTime is not a function

Open ilvenini opened this issue 1 year ago • 2 comments

Bug description

Given this table visualization with time comparison enabled: image

When I click on the date column header to sort data by date (column is a TIMESTAMP in Postgres), I receive this error: image

How to reproduce the bug

  1. Create table visualization with datetime dimension
  2. Add Time comparison
  3. Click on datetime header to sort data

Screenshots/recordings

No response

Superset version

master / latest-dev

Python version

3.9

Node version

16

Browser

Chrome

Additional context

Using docker image superset:e8f5d76-dev

Checklist

  • [X] I have searched Superset docs and Slack and didn't find a solution to my problem.
  • [X] I have searched the GitHub issue tracker and didn't find a similar bug report.
  • [ ] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.

ilvenini avatar Aug 21 '24 08:08 ilvenini

@ilvenini Hello there! I'm a bot designed to help you with bug fixes, answer questions, and guide you on becoming a contributor. I'm here to assist you while waiting for a human maintainer.

To resolve the "TypeError: o.getTime is not a function" error when sorting a datetime column in a table visualization with time comparison enabled in Superset, ensure that the datetime values being processed are correctly parsed into Date objects. The error typically occurs when a non-date value is passed to a function expecting a Date object.

In the provided code, the parseDttmToDate function is responsible for converting datetime strings into Date objects. Ensure that this function is correctly used to parse all datetime values before any operations that require Date objects.

Here is an example of how you can use the parseDttmToDate function to ensure datetime values are correctly parsed:

const parsedDate = parseDttmToDate(dttmString);
if (parsedDate && !Number.isNaN(parsedDate.getTime())) {
  // Proceed with operations that require a Date object
} else {
  // Handle the case where the datetime string could not be parsed
  console.error("Invalid datetime string:", dttmString);
}

In the getTimeOffset function, ensure that the parseDttmToDate function is used to parse the startStr and endStr values:

const [startStr, endStr] = (timeRangeFilter?.comparator ?? '')
  .split(' : ')
  .map((date: string) => date.trim());

const filterStartDateTime = (parseDttmToDate(startStr, false, false))?.getTime() || 0;
const filterEndDateTime = (parseDttmToDate(endStr || startStr, true, false))?.getTime() || 0;

Additionally, ensure that datetime values are correctly processed in the processDataRecords function. This function converts datetime values into a custom DateWithFormatter class, which allows for proper sorting and formatting:

const processDataRecords = memoizeOne(function processDataRecords(
  data: DataRecord[] | undefined,
  columns: DataColumnMeta[],
) {
  if (!data?.[0]) {
    return data || [];
  }
  const timeColumns = columns.filter(
    column => column.dataType === GenericDataType.Temporal,
  );

  if (timeColumns.length > 0) {
    return data.map(x => {
      const datum = { ...x };
      timeColumns.forEach(({ key, formatter }) => {
        // Convert datetime with a custom date class so we can use `String(...)`
        // formatted value for global search, and `date.getTime()` for sorting.
        datum[key] = new DateWithFormatter(x[key], {
          formatter: formatter as TimeFormatter,
        });
      });
      return datum;
    });
  }
  return data;
});

In the buildQuery function, ensure that the getTimeOffset function is correctly used to handle time comparisons:

const timeOffsets = ensureIsArray(
  isTimeComparison(formData, baseQueryObject)
    ? getTimeOffset({
        timeRangeFilter: {
          ...TimeRangeFilters[0],
          comparator:
            baseQueryObject?.time_range ??
            (TimeRangeFilters[0] as any)?.comparator,
        },
        shifts: formData.time_compare,
        startDate:
          previousCustomStartDate && !formData.start_date_offset
            ? parseDttmToDate(previousCustomStartDate)?.toUTCString()
            : formData.start_date_offset,
      })
    : [],
);

In the TimeTable component, ensure that datetime values are correctly processed and sorted. The sortNumberWithMixedTypes function is used to handle sorting of numeric values, including datetime values:

const sortNumberWithMixedTypes = (rowA, rowB, columnId, descending) =>
  sortNumericValues(
    rowA.values[columnId].props['data-value'],
    rowB.values[columnId].props['data-value'],
    { descending, nanTreatment: 'asSmallest' },
  ) *
  // react-table sort function always expects -1 for smaller number
  (descending ? -1 : 1);

By ensuring that all datetime strings are correctly parsed into Date objects and processed appropriately, you can avoid the "TypeError: o.getTime is not a function" error [1][2][3][4].

To continue talking to Dosu, mention @dosu.

dosubot[bot] avatar Aug 21 '24 08:08 dosubot[bot]

We also encounter this bug, the workaround is to convert the date value to string at the SQL level eg: TO_CHAR(date_column, 'YYYY-MM-DD')

changwuf31 avatar Aug 22 '24 04:08 changwuf31

This has been silent for 200+ days, and I think the last comment is the correct answer... cast dates to the correct format, and all should be well.

rusackas avatar Apr 03 '25 21:04 rusackas

This workaround did not work: TO_CHAR(date_column, 'YYYY-MM-DD'). Seems like a fix would make superset easier to use.

imedwei avatar Apr 30 '25 03:04 imedwei