Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Invalid Date Display in Table Widget's Date Column When Using Unix Timestamp (ms) #36455

Merged
merged 22 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5ab2834
adjust moment inputFormat for Unix ms + allow validate unix ms in tra…
jacquesikot Sep 19, 2024
1a4ebe9
add enum for MomentDateInputFormat
jacquesikot Sep 19, 2024
c97031f
Merge branch 'release' of https://github.com/appsmithorg/appsmith int…
jacquesikot Sep 19, 2024
6b6b7a8
add base test
jacquesikot Sep 19, 2024
94ef32d
improve test
jacquesikot Sep 19, 2024
da89194
improve Date_column_editing_2_spec
jacquesikot Sep 20, 2024
2621b9a
complete test
jacquesikot Sep 20, 2024
877f4ce
extract helper and fixtures in test case
jacquesikot Sep 23, 2024
68facfb
improve transformDataPureFn - remove redundant !isNumber(value) check…
jacquesikot Sep 23, 2024
33da955
improve transformDataPureFn - remove redundant !isNumber(value) check…
jacquesikot Sep 23, 2024
d6ec6ae
add base test for transformDataPureFn
jacquesikot Sep 23, 2024
2ec53d1
complete transformDataPureFn unit test
jacquesikot Sep 23, 2024
4b5f373
extract test fixtures
jacquesikot Sep 23, 2024
aca19a8
remove TODO comment
jacquesikot Sep 23, 2024
36c32d6
add formattedDate to isNewRow
jacquesikot Sep 24, 2024
d8701f2
fix: extract momentAdjustedInputFormat into a function in DateCell
jacquesikot Sep 24, 2024
17fa7e9
extend test to cover all table column date types
jacquesikot Sep 25, 2024
2abc737
fix test case and move fixtures
jacquesikot Sep 27, 2024
17e8f41
remove .only from test
jacquesikot Sep 27, 2024
dfe7b65
check unit test
jacquesikot Sep 27, 2024
b13f966
add helper in Table.ts
jacquesikot Sep 27, 2024
6a3cae7
fix lint errors
jacquesikot Sep 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { tableDateColumnTypes } from "../../../../../fixtures/tableDateColumnTypes";
import {
agHelper,
entityExplorer,
propPane,
table,
} from "../../../../../support/Objects/ObjectsCore";

import EditorNavigation, {
EntityType,
} from "../../../../../support/Pages/EditorNavigation";

describe(
"Table widget date column type validation",
{ tags: ["@tag.Widget", "@tag.Table"] },
() => {
before(() => {
entityExplorer.DragNDropWidget("tablewidgetv2", 350, 500);
EditorNavigation.SelectEntityByName("Table1", EntityType.Widget);
propPane.ToggleJSMode("Table data", true);
propPane.UpdatePropertyFieldValue("Table data", tableDateColumnTypes);
table.EditColumn("unixs", "v2");
});

beforeEach(() => {
propPane.NavigateBackToPropertyPane(false);
});

const setEditableDateFormats = (format: string) => {
// Update date format property
propPane.ToggleJSMode("Date format", true);
propPane.UpdatePropertyFieldValue("Date format", format);

// Update display format property
propPane.ToggleJSMode("Display format", true);
propPane.UpdatePropertyFieldValue("Display format", "YYYY-MM-DD");

// Toggle editable
propPane.TogglePropertyState("Editable", "On");
};

const clickAndValidateDateCell = (row: number, column: number) => {
// Click unix cell edit
table.ClickOnEditIcon(row, column);

// Click on specific date within
agHelper.GetNClick(
`${table._dateInputPopover} [aria-label='${table.getFormattedTomorrowDates().verboseFormat}']`,
);

// Check that date is set in column
table
.ReadTableRowColumnData(row, column, "v2")
.then((val) =>
expect(val).to.equal(table.getFormattedTomorrowDates().isoFormat),
);
};

it("1. should allow inline editing of Unix Timestamp in seconds (unix/s)", () => {
table.ChangeColumnType("unixs", "Date");
setEditableDateFormats("Epoch");
clickAndValidateDateCell(0, 0);
});

it("2. should allow inline editing of Unix Timestamp in milliseconds (unix/ms)", () => {
table.ChangeColumnType("unixms", "Date");
setEditableDateFormats("Milliseconds");
clickAndValidateDateCell(0, 1);
});

it("3. should allow inline editing of date in YYYY-MM-DD format", () => {
table.EditColumn("yyyymmdd", "v2");
setEditableDateFormats("YYYY-MM-DD");
clickAndValidateDateCell(0, 2);
});

it("4. should allow inline editing of date in YYYY-MM-DD HH:mm format", () => {
table.EditColumn("yyyymmddhhmm", "v2");
setEditableDateFormats("YYYY-MM-DD HH:mm");
clickAndValidateDateCell(0, 3);
});

it("5. should allow inline editing of date in ISO 8601 format (YYYY-MM-DDTHH:mm:ss)", () => {
table.EditColumn("iso8601", "v2");
setEditableDateFormats("YYYY-MM-DDTHH:mm:ss");
clickAndValidateDateCell(0, 4);
});

it("6. should allow inline editing of date in YYYY-MM-DD HH:mm format", () => {
table.EditColumn("yyyymmddTHHmmss", "v2");
setEditableDateFormats("YYYY-MM-DD HH:mm");
clickAndValidateDateCell(0, 5);
});

it("7. should allow inline editing of date in 'do MMM yyyy' format", () => {
table.ChangeColumnType("yyyymmddhhmmss", "Date");
setEditableDateFormats("YYYY-MM-DDTHH:mm:ss");
clickAndValidateDateCell(0, 6);
});

it("8. should allow inline editing of date in DD/MM/YYYY format", () => {
table.ChangeColumnType("doMMMyyyy", "Date");
setEditableDateFormats("Do MMM YYYY");
clickAndValidateDateCell(0, 7);
});

it("9. should allow inline editing of date in DD/MM/YYYY HH:mm format", () => {
table.EditColumn("ddmmyyyy", "v2");
setEditableDateFormats("DD/MM/YYYY");
clickAndValidateDateCell(0, 8);
});

it("10. should allow inline editing of date in LLL (Month Day, Year Time) format", () => {
table.EditColumn("ddmmyyyyhhmm", "v2");
setEditableDateFormats("DD/MM/YYYY HH:mm");
clickAndValidateDateCell(0, 9);
});

it("11. should allow inline editing of date in LL (Month Day, Year) format", () => {
table.EditColumn("lll", "v2");
setEditableDateFormats("LLL");
clickAndValidateDateCell(0, 10);
});

it("12. should allow inline editing of date in 'D MMMM, YYYY' format", () => {
table.EditColumn("ll", "v2");
setEditableDateFormats("LL");
clickAndValidateDateCell(0, 11);
});

it("13. should allow inline editing of date in 'h:mm A D MMMM, YYYY' format", () => {
table.EditColumn("dmmmmyyyy", "v2");
setEditableDateFormats("D MMMM, YYYY");
clickAndValidateDateCell(0, 12);
});

it("14. should allow inline editing of date in MM-DD-YYYY format", () => {
table.EditColumn("hmmAdmmmmyyyy", "v2");
setEditableDateFormats("H:mm A D MMMM, YYYY");
clickAndValidateDateCell(0, 13);
});

it("15. should allow inline editing of date in DD-MM-YYYY format", () => {
table.EditColumn("mm1dd1yyyy", "v2");
setEditableDateFormats("MM-DD-YYYY");
clickAndValidateDateCell(0, 14);
});
},
);
26 changes: 26 additions & 0 deletions app/client/cypress/fixtures/tableDateColumnTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const tableDateColumnTypes = `
{{
[
{
"unixs": 1727212200,
"unixms": 1727212200000,
"yyyymmdd": "2024-09-25",
"yyyymmddhhmm": "2024-09-25 14:30",
iso8601: "2024-09-25T14:30:00.000Z",
"yyyymmddTHHmmss": "2024-09-25T14:30:00",
"yyyymmddhhmmss": "2024-09-25 02:30:00",
"doMMMyyyy": "25th Sep 2024",
"ddmmyyyy": "25/09/2024",
"ddmmyyyyhhmm": "25/09/2024 14:30",
lll: "September 25, 2024 2:30 PM",
ll: "September 25, 2024",
"dmmmmyyyy": "25 September, 2024",
"hmmAdmmmmyyyy": "2:30 PM 25 September, 2024",
"mm1dd1yyyy": "09-25-2024",
"dd1mm1yyyy": "25-09-2024",
"ddimmiyy": "25/09/24",
"mmddyy": "09/25/24",
},
]
}}
`;
34 changes: 34 additions & 0 deletions app/client/cypress/support/Pages/Table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -848,4 +848,38 @@ export class Table {
this.agHelper.GetHoverNClick(selector, 1, true);
verify && cy.get(selector).eq(1).should("be.disabled");
}

/**
* Helper function to get formatted date strings for tomorrow's date.
*
* @returns {Object} An object containing:
* - verbose format (e.g., "Sat Sep 21 2024")
* - ISO date format (e.g., "2024-09-21")
*/
public getFormattedTomorrowDates() {
// Create a new Date object for today
const tomorrow = new Date();

// Set the date to tomorrow by adding 1 to today's date
tomorrow.setDate(tomorrow.getDate() + 1);

// Format tomorrow's date in verbose form (e.g., "Sat Sep 21 2024")
const verboseFormat = tomorrow
.toLocaleDateString("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "2-digit",
})
.replace(/,/g, ""); // Remove commas from the formatted string

// Format tomorrow's date in ISO form (e.g., "2024-09-21")
const isoFormat = tomorrow.toISOString().split("T")[0]; // Extract the date part only

// Return both formatted date strings as an object
return {
verboseFormat,
isoFormat,
};
}
Comment on lines +859 to +884
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Class, let's improve our new method!

The getFormattedTomorrowDates method is a fine addition to our Table class. However, we can make it even better with a few tweaks:

  1. Let's rename it to getFormattedTomorrowDateStrings to be more specific about returning multiple formats.

  2. We should use JSDoc comments for better documentation. Here's an example:

/**
 * Gets formatted date strings for tomorrow's date.
 * @returns {Object} An object containing formatted date strings.
 * @property {string} verboseFormat - Date in verbose format (e.g., "Sat Sep 21 2024").
 * @property {string} isoFormat - Date in ISO format (e.g., "2024-09-21").
 */
  1. Let's add TypeScript type annotations for better type safety:
public getFormattedTomorrowDateStrings(): { verboseFormat: string; isoFormat: string } {
  // ... method implementation ...
}
  1. Consider using padStart for consistent day formatting:
day: "2-digit" -> day: (tomorrow.getDate().toString().padStart(2, '0'))

Now, who would like to come to the board and implement these improvements?

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import DateComponent from "widgets/DatePickerWidget2/component";
import { TimePrecision } from "widgets/DatePickerWidget2/constants";
import type { RenderDefaultPropsType } from "./PlainTextCell";
import styled from "styled-components";
import { EditableCellActions } from "widgets/TableWidgetV2/constants";
import {
DateInputFormat,
EditableCellActions,
MomentDateInputFormat,
} from "widgets/TableWidgetV2/constants";
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
import moment from "moment";
import { BasicCell } from "./BasicCell";
Expand Down Expand Up @@ -196,6 +200,19 @@ export const DateCell = (props: DateComponentProps) => {
const [isValid, setIsValid] = useState(true);
const [showRequiredError, setShowRequiredError] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);

const convertInputFormatToMomentFormat = (inputFormat: string) => {
let momentAdjustedInputFormat = inputFormat;

if (inputFormat === DateInputFormat.MILLISECONDS) {
momentAdjustedInputFormat = MomentDateInputFormat.MILLISECONDS;
} else if (inputFormat === DateInputFormat.EPOCH) {
momentAdjustedInputFormat = MomentDateInputFormat.SECONDS;
}

return momentAdjustedInputFormat;
};

const isCellCompletelyValid = useMemo(
() => isEditableCellValid && isValid,
[isEditableCellValid, isValid],
Expand All @@ -218,8 +235,15 @@ export const DateCell = (props: DateComponentProps) => {
}, [value, props.outputFormat]);

const onDateSelected = (date: string) => {
const momentAdjustedInputFormat =
convertInputFormatToMomentFormat(inputFormat);

const formattedDate = date
? moment(date).format(momentAdjustedInputFormat)
: "";

if (isNewRow) {
updateNewRowValues(alias, date, date);
updateNewRowValues(alias, date, formattedDate);

return;
}
Expand All @@ -235,8 +259,6 @@ export const DateCell = (props: DateComponentProps) => {
setShowRequiredError(false);
setHasFocus(false);

const formattedDate = date ? moment(date).format(inputFormat) : "";

onDateSave(rowIndex, alias, formattedDate, onDateSelectedString);
};

Expand Down
5 changes: 5 additions & 0 deletions app/client/src/widgets/TableWidgetV2/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ export enum DateInputFormat {
MILLISECONDS = "Milliseconds",
}

export enum MomentDateInputFormat {
MILLISECONDS = "x",
SECONDS = "X",
}

export const defaultEditableCell: EditableCell = {
column: "",
index: -1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { ReactTableColumnProps } from "widgets/TableWidgetV2/component/Constants";
import {
columns,
columnsNonDate,
expectedDataNonDate,
tableDataNonDate,
} from "./fixtures";
import { transformDataPureFn } from "./transformDataPureFn";

describe("transformDataPureFn", () => {
it("should handle invalid date values", () => {
const invalidTableData = [
{
epoch: "invalid_epoch",
milliseconds: "invalid_milliseconds",
iso_8601: "invalid_iso_8601",
yyyy_mm_dd: "invalid_date",
lll: "invalid_date",
},
];

const expectedInvalidData = [
{
epoch: "Invalid date",
milliseconds: "Invalid date",
iso_8601: "8601-01-01",
yyyy_mm_dd: "Invalid date",
lll: "Invalid date",
},
];

const result = transformDataPureFn(
invalidTableData,
columns as ReactTableColumnProps[],
);

expect(result).toEqual(expectedInvalidData);
});

it("should return an empty array when tableData is empty", () => {
const result = transformDataPureFn([], columns as ReactTableColumnProps[]);

expect(result).toEqual([]);
});

it("should not transform non-date data", () => {
const result = transformDataPureFn(
tableDataNonDate,
columnsNonDate as ReactTableColumnProps[],
);

expect(result).toEqual(expectedDataNonDate);
});
});
Loading
Loading