Skip to content
Snippets Groups Projects
Unverified Commit bfc51d2a authored by Denis Berezin's avatar Denis Berezin Committed by GitHub
Browse files

Added support for first-day-of-week setting in expression date picker (#29413)

parent a913f745
No related branches found
No related tags found
No related merge requests found
......@@ -128,6 +128,15 @@ export interface TokenStatus {
status?: TokenStatusStatus;
}
export type DayOfWeekId =
| "sunday"
| "monday"
| "tuesday"
| "wednesday"
| "thursday"
| "friday"
| "saturday";
export interface TokenFeatures {
advanced_config: boolean;
advanced_permissions: boolean;
......@@ -212,6 +221,7 @@ export interface Settings {
"slack-files-channel": string | null;
"slack-token": string | null;
"slack-token-valid?": boolean;
"start-of-week"?: DayOfWeekId;
"subscription-allowed-domains": string | null;
"token-features": TokenFeatures;
"token-status": TokenStatus | null;
......
/* eslint-disable react/prop-types */
import React, { Component } from "react";
import "./Calendar.css";
import cx from "classnames";
import moment, { Moment } from "moment-timezone";
import { t } from "ttag";
import Icon from "metabase/components/Icon";
import {
getDayOfWeekOptions,
getFirstDayOfWeekIndex,
} from "metabase/lib/date-time";
import "./Calendar.css";
import { CalendarDay } from "./Calendar.styled";
export type SelectAll = "after" | "before";
......@@ -133,13 +135,17 @@ export default class Calendar extends Component<Props, State> {
}
renderDayNames() {
// translator: weekdays abbreviations
const names = [t`Su`, t`Mo`, t`Tu`, t`We`, t`Th`, t`Fr`, t`Sa`];
const days = getDayOfWeekOptions();
return (
<div className="Calendar-day-names Calendar-week py1">
{names.map(name => (
<span key={name} className="Calendar-day-name text-centered">
{name}
{days.map(({ shortName }) => (
<span
key={shortName}
className="Calendar-day-name text-centered"
data-testid="calendar-day-name"
>
{shortName}
</span>
))}
</div>
......@@ -149,7 +155,9 @@ export default class Calendar extends Component<Props, State> {
renderWeeks(current?: Moment) {
current = current || moment();
const weeks = [];
const date = moment(current).startOf("month").day("Sunday");
const firstDayOfWeek = getFirstDayOfWeekIndex();
const date = moment(current).startOf("month").isoWeekday(firstDayOfWeek);
let done = false;
let monthIndex = date.month();
let count = 0;
......@@ -173,7 +181,11 @@ export default class Calendar extends Component<Props, State> {
monthIndex = date.month();
}
return <div className="Calendar-weeks relative">{weeks}</div>;
return (
<div className="Calendar-weeks relative" data-testid="calendar-weeks">
{weeks}
</div>
);
}
renderCalender(current?: Moment, side?: "left" | "right") {
......
import React from "react";
import { render, screen } from "__support__/ui";
import MetabaseSettings from "metabase/lib/settings";
import { updateMomentStartOfWeek } from "metabase/lib/i18n";
import Calendar from "./Calendar";
describe("Calendar", () => {
it("should render weekday short names", () => {
setup();
expect(
screen
.getAllByTestId("calendar-day-name")
.map(dayEl => dayEl.textContent),
).toEqual(["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]);
});
describe('with custom "start-of-week" setting', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2023-03-23T08:00:00"));
MetabaseSettings.set("start-of-week", "wednesday");
updateMomentStartOfWeek();
});
afterEach(() => {
MetabaseSettings.set("start-of-week", "sunday"); // rollback to default
updateMomentStartOfWeek();
jest.useRealTimers();
});
it("should render days based on first day of the week settings", () => {
setup();
expect(
screen
.getAllByTestId("calendar-day-name")
.map((dayEl, index) => dayEl.textContent),
).toEqual(["We", "Th", "Fr", "Sa", "Su", "Mo", "Tu"]);
// check that listed dates are correct and start with proper day-of-week
expect(screen.getByTestId("calendar-weeks")).toHaveTextContent(
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 1, 2, 3, 4,
].join(""), // days in March 2023 + days of the first week of April until Wednesday (not including it)
);
});
});
});
function setup() {
return render(<Calendar />);
}
import React, { Component } from "react";
import { t } from "ttag";
import _ from "underscore";
import Select, { SelectChangeEvent } from "metabase/core/components/Select";
import { SegmentedControl } from "metabase/components/SegmentedControl";
import { capitalize } from "metabase/lib/formatting/strings";
import type {
ScheduleDayType,
ScheduleFrameType,
ScheduleType,
ScheduleSettings,
ScheduleType,
} from "metabase-types/api";
import {
AM_PM_OPTIONS,
getDayOfWeekOptions,
HOUR_OPTIONS,
MINUTE_OPTIONS,
MONTH_DAY_OPTIONS,
} from "metabase/lib/date-time";
import {
PickerText,
PickerRoot,
PickerRow,
PickerSpacedRow,
PickerText,
ScheduleDescriptionContainer,
} from "./SchedulePicker.styled";
export const HOUR_OPTIONS = _.times(12, n => ({
name: (n === 0 ? 12 : n) + ":00",
value: n,
}));
export const MINUTE_OPTIONS = _.times(60, n => ({
name: n.toString(),
value: n,
}));
export const AM_PM_OPTIONS = [
{ name: "AM", value: 0 },
{ name: "PM", value: 1 },
];
export const DAY_OF_WEEK_OPTIONS = [
{ name: t`Sunday`, value: "sun" },
{ name: t`Monday`, value: "mon" },
{ name: t`Tuesday`, value: "tue" },
{ name: t`Wednesday`, value: "wed" },
{ name: t`Thursday`, value: "thu" },
{ name: t`Friday`, value: "fri" },
{ name: t`Saturday`, value: "sat" },
];
export const MONTH_DAY_OPTIONS = [
{ name: t`First`, value: "first" },
{ name: t`Last`, value: "last" },
{ name: t`15th (Midpoint)`, value: "mid" },
];
const optionNameTranslations = {
hourly: t`Hourly`,
daily: t`Daily`,
......@@ -155,7 +128,7 @@ class SchedulePicker extends Component<SchedulePickerProps> {
const DAY_OPTIONS = [
{ name: t`Calendar Day`, value: null },
...DAY_OF_WEEK_OPTIONS,
...getDayOfWeekOptions(),
];
return (
......@@ -194,7 +167,7 @@ class SchedulePicker extends Component<SchedulePickerProps> {
onChange={(e: SelectChangeEvent<ScheduleDayType>) =>
this.handleChangeProperty("schedule_day", e.target.value)
}
options={DAY_OF_WEEK_OPTIONS}
options={getDayOfWeekOptions()}
/>
</PickerRow>
);
......
import moment from "moment-timezone";
import _ from "underscore";
import { t } from "ttag";
import { DayOfWeekId } from "metabase-types/api";
// returns 0-6 where Sunday as 0 and Saturday as 6
// Note: Keep in mind that this relays on moment internal state, which is not ideal.
export const getFirstDayOfWeekIndex = (): number => {
return moment().startOf("week").isoWeekday();
};
type DayOfWeekOption = {
name: string;
shortName: string;
value: string;
id: DayOfWeekId;
};
export const DAY_OF_WEEK_OPTIONS: DayOfWeekOption[] = [
{ name: t`Sunday`, shortName: t`Su`, value: "sun", id: "sunday" },
{ name: t`Monday`, shortName: t`Mo`, value: "mon", id: "monday" },
{ name: t`Tuesday`, shortName: t`Tu`, value: "tue", id: "tuesday" },
{ name: t`Wednesday`, shortName: t`We`, value: "wed", id: "wednesday" },
{ name: t`Thursday`, shortName: t`Th`, value: "thu", id: "thursday" },
{ name: t`Friday`, shortName: t`Fr`, value: "fri", id: "friday" },
{ name: t`Saturday`, shortName: t`Sa`, value: "sat", id: "saturday" },
];
export const getDayOfWeekOptions = (): DayOfWeekOption[] => {
const firstDayOfWeek = getFirstDayOfWeekIndex();
if (firstDayOfWeek === 0) {
return DAY_OF_WEEK_OPTIONS;
}
return [
...DAY_OF_WEEK_OPTIONS.slice(firstDayOfWeek),
...DAY_OF_WEEK_OPTIONS.slice(0, firstDayOfWeek),
];
};
export const HOUR_OPTIONS = _.times(12, n => ({
name: (n === 0 ? 12 : n) + ":00",
value: n,
}));
export const MINUTE_OPTIONS = _.times(60, n => ({
name: n.toString(),
value: n,
}));
export const AM_PM_OPTIONS = [
{ name: "AM", value: 0 },
{ name: "PM", value: 1 },
];
export const MONTH_DAY_OPTIONS = [
{ name: t`First`, value: "first" },
{ name: t`Last`, value: "last" },
{ name: t`15th (Midpoint)`, value: "mid" },
];
......@@ -2,6 +2,7 @@ import { addLocale, useLocale } from "ttag";
import moment from "moment-timezone";
import MetabaseSettings from "metabase/lib/settings";
import { DAY_OF_WEEK_OPTIONS } from "metabase/lib/date-time";
// note this won't refresh strings that are evaluated at load time
export async function loadLocalization(locale) {
......@@ -26,23 +27,15 @@ export async function loadLocalization(locale) {
}
// Tell Moment.js to use the value of the start-of-week Setting for its current locale
function updateMomentStartOfWeek() {
export function updateMomentStartOfWeek() {
const startOfWeekDayName = MetabaseSettings.get("start-of-week");
if (!startOfWeekDayName) {
return;
}
const START_OF_WEEK_DAYS = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
const startOfWeekDayNumber = START_OF_WEEK_DAYS.indexOf(startOfWeekDayName);
const startOfWeekDayNumber = DAY_OF_WEEK_OPTIONS.findIndex(
({ id }) => id === startOfWeekDayName,
);
if (startOfWeekDayNumber === -1) {
return;
}
......
......@@ -10,9 +10,9 @@ import { getUser } from "metabase/selectors/user";
import { deleteAlert, unsubscribeFromAlert } from "metabase/alert/alert";
import {
AM_PM_OPTIONS,
DAY_OF_WEEK_OPTIONS,
getDayOfWeekOptions,
HOUR_OPTIONS,
} from "metabase/containers/SchedulePicker";
} from "metabase/lib/date-time";
import Icon from "metabase/components/Icon";
import Modal from "metabase/components/Modal";
import {
......@@ -274,8 +274,10 @@ export class AlertScheduleText extends Component {
return `${verbose ? "daily at " : "Daily, "} ${hour} ${amPm}`;
} else if (scheduleType === "weekly") {
const hourOfDay = schedule.schedule_hour;
const dayOfWeekOptions = getDayOfWeekOptions();
const day = _.find(
DAY_OF_WEEK_OPTIONS,
dayOfWeekOptions,
o => o.value === schedule.schedule_day,
).name;
const hour = _.find(
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment