Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

DEVPROD-838: Add user time format preference #2280

Merged
merged 7 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 1 addition & 1 deletion src/components/Header/UserDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const UserDropdown = () => {
},
];
if (permissions?.canEditAdminSettings) {
menuItems.splice(1, 0, {
menuItems.splice(2, 0, {
"data-cy": "admin-link",
text: "Admin",
href: adminSettingsURL,
Expand Down
17 changes: 12 additions & 5 deletions src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export const LeafyGreenSelect: React.FC<

export const LeafyGreenRadio: React.FC<EnumSpruceWidgetProps> = ({
disabled,
id,
label,
onChange,
options,
Expand All @@ -224,11 +225,17 @@ export const LeafyGreenRadio: React.FC<EnumSpruceWidgetProps> = ({
} = options;
return (
<ElementWrapper css={elementWrapperCSS}>
{label && (
<LabelContainer>
<Label htmlFor={id}>{label}</Label>
</LabelContainer>
Comment on lines +228 to +231
Copy link
Contributor

Choose a reason for hiding this comment

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

Great catch!

)}
<RadioGroup
data-cy={dataCy}
id={id}
name={label}
value={value}
onChange={(e) => onChange(e.target.value)}
data-cy={dataCy}
value={value}
>
{enumOptions.map((o) => {
const optionDisabled = enumDisabled?.includes(o.value) ?? false;
Expand Down Expand Up @@ -275,12 +282,12 @@ export const LeafyGreenRadioBox: React.FC<
return (
<ElementWrapper css={elementWrapperCSS}>
{showLabel !== false && (
<RadioBoxLabelContainer>
<LabelContainer>
<Label htmlFor={id} disabled={disabled}>
{label}
</Label>
{description && <Description>{description}</Description>}
</RadioBoxLabelContainer>
</LabelContainer>
)}
{!!errors && (
<StyledBanner variant="danger" data-cy="error-banner">
Expand Down Expand Up @@ -320,7 +327,7 @@ const StyledBanner = styled(Banner)`
margin-bottom: ${size.s};
`;

const RadioBoxLabelContainer = styled.div`
const LabelContainer = styled.div`
margin-bottom: ${size.xs};
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ exports[`Snapshot Tests SpruceForm.stories Example2 1`] = `
<div
aria-label="radio-group-"
class="leafygreen-ui-14o8ny9"
id="root_reprovisionMethod"
role="group"
>
<div
Expand Down
5 changes: 5 additions & 0 deletions src/constants/fieldMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ export const dateFormats = listOfDateFormatStrings.map((format) => ({
})}`,
}));

export enum TimeFormat {
TwelveHour = "h:mm:ss aa",
TwentyFourHour = "H:mm:ss",
}

export const notificationFields = {
patchFinish: "Patch finish",
patchFirstFailure: "Patch first task failure",
Expand Down
3 changes: 3 additions & 0 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2949,6 +2949,7 @@ export type UserSettings = {
region?: Maybe<Scalars["String"]["output"]>;
slackMemberId?: Maybe<Scalars["String"]["output"]>;
slackUsername?: Maybe<Scalars["String"]["output"]>;
timeFormat?: Maybe<Scalars["String"]["output"]>;
timezone?: Maybe<Scalars["String"]["output"]>;
useSpruceOptions?: Maybe<UseSpruceOptions>;
};
Expand All @@ -2964,6 +2965,7 @@ export type UserSettingsInput = {
region?: InputMaybe<Scalars["String"]["input"]>;
slackMemberId?: InputMaybe<Scalars["String"]["input"]>;
slackUsername?: InputMaybe<Scalars["String"]["input"]>;
timeFormat?: InputMaybe<Scalars["String"]["input"]>;
timezone?: InputMaybe<Scalars["String"]["input"]>;
useSpruceOptions?: InputMaybe<UseSpruceOptionsInput>;
};
Expand Down Expand Up @@ -8720,6 +8722,7 @@ export type UserSettingsQuery = {
region?: string | null;
slackMemberId?: string | null;
slackUsername?: string | null;
timeFormat?: string | null;
timezone?: string | null;
githubUser?: {
__typename?: "GithubUser";
Expand Down
1 change: 1 addition & 0 deletions src/gql/queries/user-settings.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ query UserSettings {
region
slackMemberId
slackUsername
timeFormat
timezone
useSpruceOptions {
hasUsedMainlineCommitsBefore
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useDateFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { useUserTimeZone } from "./useUserTimeZone";
export const useDateFormat = () => {
const timezone = useUserTimeZone();
const { userSettings } = useUserSettings();
const { dateFormat } = userSettings || {};
const { dateFormat, timeFormat } = userSettings || {};

return (date: string | number | Date, options: DateCopyOptions = {}) =>
getDateCopy(date, {
tz: timezone,
dateFormat,
timeFormat,
...options,
});
};
38 changes: 35 additions & 3 deletions src/pages/preferences/preferencesTabs/ProfileTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Skeleton } from "antd";
import { usePreferencesAnalytics } from "analytics";
import { SettingsCard } from "components/SettingsCard";
import { SpruceForm } from "components/SpruceForm";
import { timeZones, dateFormats } from "constants/fieldMaps";
import { timeZones, dateFormats, TimeFormat } from "constants/fieldMaps";
import { useToastContext } from "context/toast";
import {
UpdateUserSettingsMutation,
Expand All @@ -23,8 +23,15 @@ export const ProfileTab: React.FC = () => {
const dispatchToast = useToastContext();

const { loading, userSettings } = useUserSettings();
const { dateFormat, githubUser, region, timezone } = userSettings ?? {};
const {
dateFormat,
githubUser,
region,
timeFormat: dbTimeFormat,
timezone,
} = userSettings ?? {};
const lastKnownAs = githubUser?.lastKnownAs || "";
const timeFormat = dbTimeFormat || TimeFormat.TwelveHour;

const { data: awsRegionData, loading: awsRegionLoading } =
useQuery<AwsRegionsQuery>(AWS_REGIONS);
Expand All @@ -49,11 +56,13 @@ export const ProfileTab: React.FC = () => {
region: string;
githubUser: { lastKnownAs?: string };
dateFormat: string;
timeFormat: string;
}>({
timezone,
region,
githubUser: { lastKnownAs },
dateFormat,
timeFormat,
});

useEffect(() => {
Expand All @@ -62,8 +71,9 @@ export const ProfileTab: React.FC = () => {
timezone,
region,
dateFormat,
timeFormat,
});
}, [dateFormat, githubUser, region, timezone]);
}, [dateFormat, githubUser, region, timeFormat, timezone]);

const handleSubmit = () => {
updateUserSettings({
Expand Down Expand Up @@ -111,6 +121,9 @@ export const ProfileTab: React.FC = () => {
dateFormat: {
"ui:placeholder": "Select a date format",
},
timeFormat: {
"ui:widget": "radio",
},
}}
schema={{
properties: {
Expand Down Expand Up @@ -150,6 +163,25 @@ export const ProfileTab: React.FC = () => {
})),
],
},
timeFormat: {
type: "string",
title: "Time Format",
oneOf: [
{
type: "string" as "string",
title: "12-hour clock",
description: "Display time with AM/PM, e.g. 12:34 PM",
enum: [TimeFormat.TwelveHour],
},

{
type: "string" as "string",
title: "24-hour clock",
description: "Use 24-hour notation, e.g. 13:34",
enum: [TimeFormat.TwentyFourHour],
},
],
},
},
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export const getFormSchema = (
properties: {
mergeQueue: {
type: "string" as "string",
title: "",
oneOf: [
{
type: "string" as "string",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const getFormSchema = (
properties: {
projectHealthView: {
type: "string" as "string",
title: "",
oneOf: [
{
type: "string" as "string",
Expand Down
15 changes: 10 additions & 5 deletions src/utils/string/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export type DateCopyOptions = {
omitSeconds?: boolean;
omitTimezone?: boolean;
dateFormat?: string;
timeFormat?: string;
};

/**
Expand All @@ -111,7 +112,7 @@ export type DateCopyOptions = {
* @param options.omitSeconds - if true, will not return the seconds
* @param options.omitTimezone - if true, will not return the timezone
* @param options.dateFormat - a date format string, such as "MMM d, yyyy"
* @returns - a string representing the date in the format of "MMM d, yyyy h:mm:ss a z"
* @returns - a string representing the date in either the user's specified format or the default, "MMM d, yyyy h:mm:ss aa z"
*/
export const getDateCopy = (
time: string | number | Date,
Expand All @@ -121,15 +122,19 @@ export const getDateCopy = (
return "";
}
const { dateOnly, omitSeconds, omitTimezone, tz } = options || {};
let { dateFormat } = options || {};
let { dateFormat, timeFormat } = options || {};
if (!dateFormat) {
dateFormat = "MMM d, yyyy";
}
if (!timeFormat) {
timeFormat = "h:mm:ss aa";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit. could we reuse the TimeFormat enum declared above instead of duplicating here?

}
if (omitSeconds) {
timeFormat = timeFormat.replace(":ss", "");
}
const finalDateFormat = dateOnly
? dateFormat
: `${dateFormat}, h:mm${omitSeconds ? "" : ":ss"} aa${
omitTimezone ? "" : " z"
}`;
: `${dateFormat}, ${timeFormat}${omitTimezone ? "" : " z"}`;
if (tz) {
return format(utcToZonedTime(time, tz), finalDateFormat, {
timeZone: tz,
Expand Down
19 changes: 19 additions & 0 deletions src/utils/string/string.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TimeFormat } from "constants/fieldMaps";
import {
msToDuration,
sortFunctionDate,
Expand Down Expand Up @@ -292,7 +293,25 @@ describe("getDateCopy", () => {
getDateCopy("08/31/1996", { dateFormat: "MM/dd/yyyy", dateOnly: true }),
).toBe("08/31/1996");
});

it("returns dates with a custom time format when supplied with the option", () => {
expect(
getDateCopy(new Date("2020-11-16T22:17:29z"), {
omitTimezone: true,
timeFormat: TimeFormat.TwentyFourHour,
}),
).toBe("Nov 16, 2020, 22:17:29");

expect(
getDateCopy(new Date("2020-11-16T22:17:29z"), {
omitSeconds: true,
omitTimezone: true,
timeFormat: TimeFormat.TwelveHour,
}),
).toBe("Nov 16, 2020, 10:17 PM");
});
});

describe("applyStrictRegex", () => {
it("converts string to strict regex", () => {
expect(applyStrictRegex("dog")).toBe("^dog$");
Expand Down
Loading