diff --git a/apps/spruce/src/components/Spawn/editHostModal/getFormSchema.tsx b/apps/spruce/src/components/Spawn/editHostModal/getFormSchema.tsx
index 7f0ded4c0..a1e3ff2bf 100644
--- a/apps/spruce/src/components/Spawn/editHostModal/getFormSchema.tsx
+++ b/apps/spruce/src/components/Spawn/editHostModal/getFormSchema.tsx
@@ -23,7 +23,7 @@ interface Props {
myPublicKeys: MyPublicKeysQuery["myPublicKeys"];
noExpirationCheckboxTooltip: string;
permanentlyExempt: boolean;
- timeZone?: string;
+ timeZone: string;
volumes: MyVolumesQuery["myVolumes"];
}
diff --git a/apps/spruce/src/components/Spawn/editHostModal/transformer.test.ts b/apps/spruce/src/components/Spawn/editHostModal/transformer.test.ts
index 31f26d2a1..25c25103e 100644
--- a/apps/spruce/src/components/Spawn/editHostModal/transformer.test.ts
+++ b/apps/spruce/src/components/Spawn/editHostModal/transformer.test.ts
@@ -8,7 +8,6 @@ describe("edit spawn host modal", () => {
hostId: "host_id",
myPublicKeys,
oldUserTags,
- timeZone: "America/New_York",
}),
).toStrictEqual({
hostId: "host_id",
@@ -67,6 +66,9 @@ const formState = {
runContinuously: false,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
},
},
instanceType: "m4.xlarge",
diff --git a/apps/spruce/src/components/Spawn/editHostModal/transformer.ts b/apps/spruce/src/components/Spawn/editHostModal/transformer.ts
index 85d4c162e..aaff2de57 100644
--- a/apps/spruce/src/components/Spawn/editHostModal/transformer.ts
+++ b/apps/spruce/src/components/Spawn/editHostModal/transformer.ts
@@ -13,14 +13,12 @@ interface Props {
myPublicKeys: MyPublicKeysQuery["myPublicKeys"];
formData: FormState;
oldUserTags: { key: string; value: string }[];
- timeZone: string;
}
export const formToGql = ({
formData,
hostId,
myPublicKeys,
oldUserTags,
- timeZone,
}: Props): EditSpawnHostMutationVariables => {
const {
expirationDetails,
@@ -78,8 +76,6 @@ export const formToGql = ({
},
savePublicKey: !useExisting && savePublicKey,
sleepSchedule:
- noExpiration && hostUptime
- ? getSleepSchedule(hostUptime, timeZone)
- : null,
+ noExpiration && hostUptime ? getSleepSchedule(hostUptime) : null,
};
};
diff --git a/apps/spruce/src/components/Spawn/getFormSchema.tsx b/apps/spruce/src/components/Spawn/getFormSchema.tsx
index 9465cc7b8..c7d0ec6bc 100644
--- a/apps/spruce/src/components/Spawn/getFormSchema.tsx
+++ b/apps/spruce/src/components/Spawn/getFormSchema.tsx
@@ -6,7 +6,7 @@ import { add } from "date-fns";
import widgets from "components/SpruceForm/Widgets";
import { StyledLink } from "components/styles";
import { hostUptimeDocumentationUrl } from "constants/externalResources";
-import { prettifyTimeZone } from "constants/fieldMaps";
+import { abbreviateTimeZone, timeZones } from "constants/fieldMaps";
import { size } from "constants/tokens";
import { MyPublicKeysQuery } from "gql/generated/types";
import {
@@ -24,7 +24,7 @@ type HostUptimeProps = {
warnings: string[];
};
isEditModal: boolean;
- timeZone?: string;
+ timeZone: string;
};
const getHostUptimeSchema = ({
@@ -38,7 +38,7 @@ const getHostUptimeSchema = ({
properties: {
useDefaultUptimeSchedule: {
type: "boolean" as "boolean",
- title: "Use default host uptime schedule (Mon–Fri, 8am–8pm)",
+ title: `Use default host uptime schedule (Mon–Fri, 8am–8pm ${abbreviateTimeZone(timeZone)})`,
default: true,
},
sleepSchedule: {
@@ -97,7 +97,24 @@ const getHostUptimeSchema = ({
},
},
details: {
- type: "null" as "null",
+ type: "object" as "object",
+ title: "",
+ properties: {
+ timeZone: {
+ type: "string",
+ title: "Time Zone",
+ default: timeZone,
+ oneOf: timeZones.map(({ str, value }) => ({
+ type: "string" as "string",
+ title: str,
+ enum: [value],
+ })),
+ },
+
+ uptimeHours: {
+ type: "null" as "null",
+ },
+ },
},
isBetaTester: {
type: "boolean" as "boolean",
@@ -169,16 +186,35 @@ const getHostUptimeSchema = ({
},
},
details: {
- "ui:descriptionNode": (
-
- ),
- "ui:showLabel": false,
- "ui:warnings": hostUptimeWarnings?.warnings,
+ "ui:elementWrapperCSS": css`
+ align-items: flex-end;
+ display: flex;
+ gap: ${size.xs};
+ flex-wrap: wrap;
+
+ > div {
+ width: 40%;
+ }
+
+ > [role="alert"] {
+ margin-top: 0;
+ width: 100%;
+ }
+ `,
+ timeZone: {
+ "ui:allowDeselect": false,
+ "ui:sizeVariant": "xsmall",
+ },
+ uptimeHours: {
+ "ui:descriptionNode": (
+
+ ),
+ "ui:showLabel": false,
+ "ui:warnings": hostUptimeWarnings?.warnings,
+ },
},
isBetaTester: {
"ui:widget": widgets.ToggleWidget,
@@ -210,19 +246,18 @@ const getHostUptimeSchema = ({
},
});
-const Details: React.FC<{ timeZone: string; totalUptimeHours: number }> = ({
- timeZone,
+const Details: React.FC<{ totalUptimeHours: number }> = ({
totalUptimeHours,
}) => (
- All times are displayed in{" "}
- {prettifyTimeZone.get(timeZone) ?? timeZone} •{" "}
- {totalUptimeHours} host uptime hours per week
+ {" "}
+ • {totalUptimeHours} host uptime hours per week
);
const DetailsDiv = styled.div`
- margin-bottom: ${size.xs};
+ margin-bottom: 21px;
+ white-space: nowrap;
`;
type ExpirationProps = {
@@ -234,7 +269,7 @@ type ExpirationProps = {
isEditModal: boolean;
noExpirationCheckboxTooltip?: string;
permanentlyExempt?: boolean;
- timeZone?: string;
+ timeZone: string;
};
export const getExpirationDetailsSchema = ({
diff --git a/apps/spruce/src/components/Spawn/spawnHostModal/getFormSchema.tsx b/apps/spruce/src/components/Spawn/spawnHostModal/getFormSchema.tsx
index 022f90695..946f9d5ca 100644
--- a/apps/spruce/src/components/Spawn/spawnHostModal/getFormSchema.tsx
+++ b/apps/spruce/src/components/Spawn/spawnHostModal/getFormSchema.tsx
@@ -35,7 +35,7 @@ interface Props {
noExpirationCheckboxTooltip: string;
permanentlyExempt: boolean;
spawnTaskData?: SpawnTaskQuery["task"];
- timeZone?: string;
+ timeZone: string;
useSetupScript?: boolean;
useProjectSetupScript?: boolean;
userAwsRegion?: string;
diff --git a/apps/spruce/src/components/Spawn/spawnHostModal/transformer.test.ts b/apps/spruce/src/components/Spawn/spawnHostModal/transformer.test.ts
index 95d65cf16..c84f706fb 100644
--- a/apps/spruce/src/components/Spawn/spawnHostModal/transformer.test.ts
+++ b/apps/spruce/src/components/Spawn/spawnHostModal/transformer.test.ts
@@ -11,7 +11,6 @@ describe("spawn host modal", () => {
formData,
myPublicKeys,
spawnTaskData: null,
- timeZone: "America/New_York",
}),
).toStrictEqual(mutationInput);
});
@@ -26,7 +25,6 @@ describe("spawn host modal", () => {
myPublicKeys,
spawnTaskData: null,
migrateVolumeId,
- timeZone: "America/New_York",
}),
).toStrictEqual({
...mutationInput,
@@ -120,6 +118,7 @@ const data: Array<{ formData: FormState; mutationInput: SpawnHostInput }> = [
runContinuously: false,
},
},
+ details: { timeZone: "America/New_York" },
},
},
homeVolumeDetails: { selectExistingVolume: true, volumeSelect: "" },
diff --git a/apps/spruce/src/components/Spawn/spawnHostModal/transformer.ts b/apps/spruce/src/components/Spawn/spawnHostModal/transformer.ts
index e14404787..4264614c5 100644
--- a/apps/spruce/src/components/Spawn/spawnHostModal/transformer.ts
+++ b/apps/spruce/src/components/Spawn/spawnHostModal/transformer.ts
@@ -15,7 +15,6 @@ interface Props {
myPublicKeys: MyPublicKeysQuery["myPublicKeys"];
spawnTaskData?: SpawnTaskQuery["task"];
migrateVolumeId?: string;
- timeZone?: string;
}
export const formToGql = ({
formData,
@@ -23,7 +22,6 @@ export const formToGql = ({
migrateVolumeId,
myPublicKeys,
spawnTaskData,
- timeZone,
}: Props): SpawnHostMutationVariables["spawnHostInput"] => {
const {
expirationDetails,
@@ -50,8 +48,7 @@ export const formToGql = ({
noExpiration: expirationDetails?.noExpiration,
sleepSchedule:
expirationDetails?.noExpiration && hostUptime
- ? // @ts-expect-error: FIXME. This comment was added by an automated script.
- getSleepSchedule(hostUptime, timeZone)
+ ? getSleepSchedule(hostUptime)
: null,
volumeId:
migrateVolumeId ||
diff --git a/apps/spruce/src/components/Spawn/utils/hostUptime.test.ts b/apps/spruce/src/components/Spawn/utils/hostUptime.test.ts
index a025c3bfb..377be276d 100644
--- a/apps/spruce/src/components/Spawn/utils/hostUptime.test.ts
+++ b/apps/spruce/src/components/Spawn/utils/hostUptime.test.ts
@@ -13,6 +13,7 @@ describe("matchesDefaultUptimeSchedule", () => {
const sched = {
dailyStartTime: "08:00",
dailyStopTime: "20:00",
+ isBetaTester: true,
permanentlyExempt: true,
shouldKeepOff: true,
timeZone: "America/New_York",
@@ -25,6 +26,7 @@ describe("matchesDefaultUptimeSchedule", () => {
const sched = {
dailyStartTime: "08:30",
dailyStopTime: "20:00",
+ isBetaTester: true,
permanentlyExempt: true,
shouldKeepOff: true,
timeZone: "America/New_York",
@@ -37,6 +39,7 @@ describe("matchesDefaultUptimeSchedule", () => {
const sched = {
dailyStartTime: "08:00",
dailyStopTime: "21:00",
+ isBetaTester: true,
permanentlyExempt: true,
shouldKeepOff: true,
timeZone: "America/New_York",
@@ -49,6 +52,7 @@ describe("matchesDefaultUptimeSchedule", () => {
const sched = {
dailyStartTime: "08:00",
dailyStopTime: "20:00",
+ isBetaTester: false,
permanentlyExempt: true,
shouldKeepOff: true,
timeZone: "America/New_York",
@@ -79,12 +83,19 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
},
noExpiration: false,
},
},
- // @ts-expect-error
- { expirationDetails: { hostUptime: { details: { addError: f } } } },
+ {
+ expirationDetails: {
+ // @ts-expect-error
+ hostUptime: { details: { uptimeHours: { addError: f } } },
+ },
+ },
);
expect(f).toHaveBeenCalledTimes(0);
});
@@ -105,12 +116,19 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
},
noExpiration: true,
},
},
- // @ts-expect-error
- { expirationDetails: { hostUptime: { details: { addError: f } } } },
+ {
+ expirationDetails: {
+ // @ts-expect-error
+ hostUptime: { details: { uptimeHours: { addError: f } } },
+ },
+ },
);
expect(f).toHaveBeenCalledTimes(1);
});
@@ -131,12 +149,19 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
},
noExpiration: true,
},
},
- // @ts-expect-error
- { expirationDetails: { hostUptime: { details: { addError: f } } } },
+ {
+ expirationDetails: {
+ // @ts-expect-error
+ hostUptime: { details: { uptimeHours: { addError: f } } },
+ },
+ },
);
expect(f).toHaveBeenCalledTimes(1);
});
@@ -157,6 +182,9 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
},
noExpiration: true,
},
@@ -184,6 +212,9 @@ describe("validator", () => {
},
},
temporarilyExemptUntil: new Date("2024-01-05").toString(),
+ details: {
+ timeZone: "America/New_York",
+ },
},
noExpiration: true,
},
@@ -227,6 +258,9 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
temporarilyExemptUntil: new Date("2001-01-01").toString(),
},
noExpiration: true,
@@ -258,6 +292,9 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
temporarilyExemptUntil: new Date("2025-01-01").toString(),
},
noExpiration: true,
@@ -289,6 +326,9 @@ describe("validator", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
temporarilyExemptUntil: new Date("2024-01-05").toString(),
},
noExpiration: true,
@@ -332,6 +372,9 @@ describe("getHostUptimeFromGql", () => {
},
temporarilyExemptUntil: "",
isBetaTester: true,
+ details: {
+ timeZone: "America/New_York",
+ },
});
});
@@ -361,6 +404,9 @@ describe("getHostUptimeFromGql", () => {
temporarilyExemptUntil:
"Mon Jul 01 2024 00:00:00 GMT+0000 (Coordinated Universal Time)",
isBetaTester: true,
+ details: {
+ timeZone: "America/New_York",
+ },
});
});
@@ -372,7 +418,7 @@ describe("getHostUptimeFromGql", () => {
permanentlyExempt: true,
shouldKeepOff: true,
temporarilyExemptUntil: null,
- timeZone: "America/New_York",
+ timeZone: "America/Chicago",
wholeWeekdaysOff: [0, 6],
};
expect(getHostUptimeFromGql(sched)).toStrictEqual({
@@ -389,6 +435,9 @@ describe("getHostUptimeFromGql", () => {
},
temporarilyExemptUntil: "",
isBetaTester: true,
+ details: {
+ timeZone: "America/Chicago",
+ },
});
});
});
@@ -396,23 +445,23 @@ describe("getHostUptimeFromGql", () => {
describe("getSleepSchedule", () => {
it("sets the default schedule", () => {
expect(
- getSleepSchedule(
- {
- useDefaultUptimeSchedule: true,
- isBetaTester: false,
- sleepSchedule: {
- enabledWeekdays: [false, false, true, true, true, true, false],
- timeSelection: {
- runContinuously: true,
- startTime:
- "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
- stopTime:
- "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
- },
+ getSleepSchedule({
+ useDefaultUptimeSchedule: true,
+ isBetaTester: false,
+ sleepSchedule: {
+ enabledWeekdays: [false, false, true, true, true, true, false],
+ timeSelection: {
+ runContinuously: true,
+ startTime:
+ "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
+ stopTime:
+ "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
},
},
- "America/New_York",
- ),
+ details: {
+ timeZone: "America/New_York",
+ },
+ }),
).toStrictEqual({
dailyStartTime: "08:00",
dailyStopTime: "20:00",
@@ -426,23 +475,23 @@ describe("getSleepSchedule", () => {
it("sets continuously running days", () => {
expect(
- getSleepSchedule(
- {
- useDefaultUptimeSchedule: false,
- isBetaTester: false,
- sleepSchedule: {
- enabledWeekdays: [false, false, true, true, true, true, false],
- timeSelection: {
- runContinuously: true,
- startTime:
- "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
- stopTime:
- "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
- },
+ getSleepSchedule({
+ useDefaultUptimeSchedule: false,
+ isBetaTester: false,
+ sleepSchedule: {
+ enabledWeekdays: [false, false, true, true, true, true, false],
+ timeSelection: {
+ runContinuously: true,
+ startTime:
+ "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
+ stopTime:
+ "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
},
},
- "America/New_York",
- ),
+ details: {
+ timeZone: "America/New_York",
+ },
+ }),
).toStrictEqual({
dailyStartTime: "",
dailyStopTime: "",
@@ -456,23 +505,23 @@ describe("getSleepSchedule", () => {
it("sets start and stop times", () => {
expect(
- getSleepSchedule(
- {
- useDefaultUptimeSchedule: false,
- isBetaTester: true,
- sleepSchedule: {
- enabledWeekdays: [false, false, true, true, true, true, false],
- timeSelection: {
- runContinuously: false,
- startTime:
- "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
- stopTime:
- "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
- },
+ getSleepSchedule({
+ useDefaultUptimeSchedule: false,
+ isBetaTester: true,
+ sleepSchedule: {
+ enabledWeekdays: [false, false, true, true, true, true, false],
+ timeSelection: {
+ runContinuously: false,
+ startTime:
+ "Sun Dec 31 1899 08:00:00 GMT+0000 (Coordinated Universal Time)",
+ stopTime:
+ "Sun Dec 31 1899 20:00:00 GMT+0000 (Coordinated Universal Time)",
},
},
- "America/New_York",
- ),
+ details: {
+ timeZone: "America/New_York",
+ },
+ }),
).toStrictEqual({
dailyStartTime: "08:00",
dailyStopTime: "20:00",
@@ -565,6 +614,9 @@ describe("getEnabledHoursCount", () => {
runContinuously: true,
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
}),
).toStrictEqual({
enabledHoursCount: 60,
@@ -587,6 +639,9 @@ describe("getEnabledHoursCount", () => {
"Mon Jan 01 1900 20:00:00 GMT+0000 (Coordinated Universal Time)",
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
}),
).toStrictEqual({
enabledHoursCount: 65,
@@ -607,6 +662,9 @@ describe("getEnabledHoursCount", () => {
stopTime: "",
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
}),
).toStrictEqual({
enabledHoursCount: 120,
@@ -629,6 +687,9 @@ describe("getEnabledHoursCount", () => {
"Mon Jan 01 1900 03:00:00 GMT+0000 (Coordinated Universal Time)",
},
},
+ details: {
+ timeZone: "America/New_York",
+ },
}),
).toStrictEqual({
enabledHoursCount: 35,
diff --git a/apps/spruce/src/components/Spawn/utils/hostUptime.ts b/apps/spruce/src/components/Spawn/utils/hostUptime.ts
index ffdb96134..bef52e350 100644
--- a/apps/spruce/src/components/Spawn/utils/hostUptime.ts
+++ b/apps/spruce/src/components/Spawn/utils/hostUptime.ts
@@ -9,8 +9,7 @@ import {
} from "date-fns";
import { ValidateProps } from "components/SpruceForm";
import { days } from "constants/fieldMaps";
-import { SleepScheduleInput } from "gql/generated/types";
-import { Optional } from "types/utils";
+import { SleepSchedule, SleepScheduleInput } from "gql/generated/types";
import { FormState as EditFormState } from "../editHostModal";
import { FormState as SpawnFormState } from "../spawnHostModal";
@@ -37,7 +36,10 @@ export type HostUptime = {
runContinuously: boolean;
};
};
- details?: null;
+ details: {
+ timeZone: string;
+ uptimeHours?: null;
+ };
isBetaTester: boolean;
temporarilyExemptUntil?: string;
};
@@ -128,18 +130,17 @@ export const getEnabledHoursCount = (
* @param obj.sleepSchedule - sleep schedule configuration
* @param obj.temporarilyExemptUntil - temporary exemption date
* @param obj.useDefaultUptimeSchedule - boolean indicating whether user has set custom schedule
- * @param timeZone - user-defined time zone, defaults to UTC
+ * @param obj.details - details form object
+ * @param obj.details.timeZone - user-selected time zone
* @returns - sleep schedule used as input for GraphQL mutations
*/
-export const getSleepSchedule = (
- {
- isBetaTester,
- sleepSchedule,
- temporarilyExemptUntil,
- useDefaultUptimeSchedule,
- }: HostUptime,
- timeZone: string,
-): SleepScheduleInput => {
+export const getSleepSchedule = ({
+ details: { timeZone },
+ isBetaTester,
+ sleepSchedule,
+ temporarilyExemptUntil,
+ useDefaultUptimeSchedule,
+}: HostUptime): SleepScheduleInput => {
if (useDefaultUptimeSchedule) {
return {
...defaultSleepSchedule,
@@ -203,24 +204,24 @@ const toTimeString = (date: Date): string =>
minute: "2-digit",
});
-type RequiredSleepSchedule = Optional;
-
-export const defaultSleepSchedule: RequiredSleepSchedule = {
+export const defaultSleepSchedule: Omit = {
dailyStartTime: toTimeString(defaultStartDate),
dailyStopTime: toTimeString(defaultStopDate),
+ isBetaTester: false,
permanentlyExempt: false,
shouldKeepOff: false,
wholeWeekdaysOff: [0, 6],
};
export const getHostUptimeFromGql = (
- sleepSchedule: RequiredSleepSchedule,
+ sleepSchedule: SleepSchedule,
): HostUptime => {
const {
dailyStartTime,
dailyStopTime,
isBetaTester,
temporarilyExemptUntil,
+ timeZone,
wholeWeekdaysOff,
} = sleepSchedule;
@@ -253,11 +254,12 @@ export const getHostUptimeFromGql = (
runContinuously: true,
},
},
+ details: { timeZone },
};
};
export const matchesDefaultUptimeSchedule = (
- sleepSchedule: RequiredSleepSchedule,
+ sleepSchedule: SleepSchedule,
): boolean => {
const { dailyStartTime, dailyStopTime, wholeWeekdaysOff } = sleepSchedule;
@@ -323,7 +325,7 @@ export const validator = (permanentlyExempt: boolean) =>
// Return error based on whether runContinously enabled
if (timeSelection?.runContinuously) {
// @ts-expect-error
- errors.expirationDetails?.hostUptime?.details?.addError?.(
+ errors.expirationDetails?.hostUptime?.details?.uptimeHours?.addError?.(
"Please pause your host for at least 1 day per week.",
);
return errors;
@@ -332,7 +334,7 @@ export const validator = (permanentlyExempt: boolean) =>
maxUptimeHours / enabledWeekdaysCount,
);
// @ts-expect-error
- errors.expirationDetails?.hostUptime?.details?.addError?.(
+ errors.expirationDetails?.hostUptime?.details?.uptimeHours?.addError?.(
`Please reduce your host uptime to a max of ${hourlyRequirement} hours per day.`,
);
return errors;
@@ -348,7 +350,7 @@ export const validator = (permanentlyExempt: boolean) =>
* @param sleepSchedule - sleepSchedule as returned from Evergreen
* @returns boolean indicating whether the sleep schedule is effectively unset.
*/
-export const isNullSleepSchedule = (sleepSchedule: RequiredSleepSchedule) => {
+export const isNullSleepSchedule = (sleepSchedule: SleepSchedule) => {
if (!sleepSchedule) return true;
const { dailyStartTime, dailyStopTime, wholeWeekdaysOff } = sleepSchedule;
diff --git a/apps/spruce/src/constants/fieldMaps.test.ts b/apps/spruce/src/constants/fieldMaps.test.ts
new file mode 100644
index 000000000..347ab61f1
--- /dev/null
+++ b/apps/spruce/src/constants/fieldMaps.test.ts
@@ -0,0 +1,11 @@
+import { abbreviateTimeZone } from "./fieldMaps";
+
+describe("abbreviateTimeZone", () => {
+ it("returns the shortened time zone", () => {
+ expect(abbreviateTimeZone("America/New_York")).oneOf(["EST", "EDT"]);
+ });
+
+ it("catches invalid time zone", () => {
+ expect(abbreviateTimeZone("foo")).toBe("");
+ });
+});
diff --git a/apps/spruce/src/constants/fieldMaps.ts b/apps/spruce/src/constants/fieldMaps.ts
index 3fd45e10f..6f6cac8b8 100644
--- a/apps/spruce/src/constants/fieldMaps.ts
+++ b/apps/spruce/src/constants/fieldMaps.ts
@@ -172,6 +172,21 @@ export const prettifyTimeZone = new Map(
timeZones.map(({ str, value }) => [value, str]),
);
+/**
+ * abbreviateTimeZone returns the shortened version of a time zone
+ * @param tz - JS timeZone option used by toLocaleTimeString
+ * @returns - shortened string, or empty string if invalid time zone provided
+ */
+export const abbreviateTimeZone = (tz: string) => {
+ try {
+ return new Date()
+ .toLocaleTimeString("en-us", { timeZone: tz, timeZoneName: "short" })
+ .split(" ")[2];
+ } catch (e) {
+ return "";
+ }
+};
+
export const listOfDateFormatStrings = [
"MM-dd-yyyy",
"dd-MM-yyyy",
diff --git a/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.test.tsx b/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.test.tsx
index e282927dd..a620b68b8 100644
--- a/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.test.tsx
+++ b/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.test.tsx
@@ -158,6 +158,7 @@ describe("editSpawnHostModal", () => {
,
);
expect(screen.queryByDataCy("edit-spawn-host-modal")).toBeVisible();
+ expect(screen.getByText("Central Time")).toBeVisible();
await user.click(
screen.getByText("Use default host uptime schedule", {
exact: false,
@@ -309,7 +310,7 @@ const baseSpawnHost: MyHost = {
...defaultSleepSchedule,
isBetaTester: true,
temporarilyExemptUntil: null,
- timeZone: "America/New_York",
+ timeZone: "America/Chicago",
},
__typename: "Host",
};
diff --git a/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.tsx b/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.tsx
index fa9de0e5a..3d3a065df 100644
--- a/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.tsx
+++ b/apps/spruce/src/pages/spawn/spawnHost/EditSpawnHostModal.tsx
@@ -18,7 +18,6 @@ import {
useLoadFormData,
} from "components/Spawn/editHostModal";
import { SpruceForm } from "components/SpruceForm";
-import { defaultTimeZone } from "constants/fieldMaps";
import { useToastContext } from "context/toast";
import {
EditSpawnHostMutation,
@@ -42,7 +41,8 @@ export const EditSpawnHostModal: React.FC = ({
}) => {
const dispatchToast = useToastContext();
const { sendEvent } = useSpawnAnalytics();
- const timeZone = useUserTimeZone() || defaultTimeZone;
+ const timeZone =
+ useUserTimeZone() || Intl.DateTimeFormat().resolvedOptions().timeZone;
const {
disableExpirationCheckbox,
@@ -79,11 +79,10 @@ export const EditSpawnHostModal: React.FC = ({
expirationDetails: {
expiration: host.expiration ? host.expiration.toString() : null,
noExpiration: host.noExpiration,
- // @ts-expect-error: FIXME. This comment was added by an automated script.
- hostUptime: isNullSleepSchedule(host?.sleepSchedule)
- ? getHostUptimeFromGql(defaultSleepSchedule)
- : // @ts-expect-error: FIXME. This comment was added by an automated script.
- getHostUptimeFromGql(host.sleepSchedule),
+ hostUptime:
+ host?.sleepSchedule && !isNullSleepSchedule(host?.sleepSchedule)
+ ? getHostUptimeFromGql(host.sleepSchedule)
+ : getHostUptimeFromGql({ ...defaultSleepSchedule, timeZone }),
},
publicKeySection: { useExisting: true, publicKeyNameDropdown: "" },
};
@@ -122,7 +121,8 @@ export const EditSpawnHostModal: React.FC = ({
// @ts-expect-error: FIXME. This comment was added by an automated script.
noExpirationCheckboxTooltip,
permanentlyExempt: !!host.sleepSchedule?.permanentlyExempt,
- timeZone,
+ timeZone:
+ formState?.expirationDetails?.hostUptime?.details?.timeZone || timeZone,
volumes,
});
@@ -159,7 +159,6 @@ export const EditSpawnHostModal: React.FC = ({
hostId: host.id,
myPublicKeys: publicKeys,
oldUserTags: userTags,
- timeZone,
});
const [hasChanges, mutationParams] = computeDiff(
diff --git a/apps/spruce/src/pages/spawn/spawnHost/spawnHostButton/SpawnHostModal.tsx b/apps/spruce/src/pages/spawn/spawnHost/spawnHostButton/SpawnHostModal.tsx
index ee83bfdbb..2bf5629e0 100644
--- a/apps/spruce/src/pages/spawn/spawnHost/spawnHostButton/SpawnHostModal.tsx
+++ b/apps/spruce/src/pages/spawn/spawnHost/spawnHostButton/SpawnHostModal.tsx
@@ -16,7 +16,6 @@ import {
FormState,
} from "components/Spawn/spawnHostModal";
import { SpruceForm } from "components/SpruceForm";
-import { defaultTimeZone } from "constants/fieldMaps";
import { useToastContext } from "context/toast";
import {
SpawnHostMutation,
@@ -41,7 +40,8 @@ export const SpawnHostModal: React.FC = ({
}) => {
const dispatchToast = useToastContext();
const spawnAnalytics = useSpawnAnalytics();
- const timeZone = useUserTimeZone() || defaultTimeZone;
+ const timeZone =
+ useUserTimeZone() || Intl.DateTimeFormat().resolvedOptions().timeZone;
// Handle distroId, taskId query param
const { search } = useLocation();
@@ -118,7 +118,8 @@ export const SpawnHostModal: React.FC = ({
isMigration: false,
isVirtualWorkstation: !!selectedDistro?.isVirtualWorkStation,
spawnTaskData: spawnTaskData?.task,
- timeZone,
+ timeZone:
+ formState?.expirationDetails?.hostUptime?.details?.timeZone || timeZone,
useSetupScript: !!formState?.setupScriptSection?.defineSetupScriptCheckbox,
useProjectSetupScript: !!formState?.loadData?.runProjectSpecificSetupScript,
});