parent
c2aa4e6319
commit
d144591dbf
@ -5,6 +5,7 @@
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@cheap-glitch/mi-cron": "^1.0.1",
|
||||
"@formatjs/intl-displaynames": "^5.2.5",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||
"@lit/localize": "^0.11.1",
|
||||
@ -12,8 +13,6 @@
|
||||
"@xstate/fsm": "^1.6.2",
|
||||
"axios": "^0.22.0",
|
||||
"color": "^4.0.1",
|
||||
"cron-parser": "^4.2.1",
|
||||
"cronstrue": "^1.123.0",
|
||||
"fuse.js": "^6.5.3",
|
||||
"lit": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@ -1,14 +1,25 @@
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronstrue from "cronstrue"; // TODO localize
|
||||
import { parseCron } from "@cheap-glitch/mi-cron";
|
||||
|
||||
import LiteElement, { html } from "../utils/LiteElement";
|
||||
import { getLocaleTimeZone } from "../utils/localization";
|
||||
import {
|
||||
ScheduleInterval,
|
||||
getScheduleInterval,
|
||||
getUTCSchedule,
|
||||
humanizeSchedule,
|
||||
humanizeNextDate,
|
||||
} from "../utils/cron";
|
||||
import type { CrawlTemplate } from "../pages/archive/types";
|
||||
|
||||
const nowHour = new Date().getHours();
|
||||
const initialHours = nowHour % 12 || 12;
|
||||
const initialPeriod = nowHour > 11 ? "PM" : "AM";
|
||||
const hours = Array.from({ length: 12 }).map((x, i) => ({
|
||||
value: i + 1,
|
||||
label: `${i + 1}`,
|
||||
}));
|
||||
const minutes = Array.from({ length: 60 }).map((x, i) => ({
|
||||
value: i,
|
||||
label: `${i}`.padStart(2, "0"),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
@ -36,49 +47,28 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
cancelable?: boolean = false;
|
||||
|
||||
@state()
|
||||
private editedSchedule?: string;
|
||||
private scheduleInterval: ScheduleInterval | "" = "";
|
||||
|
||||
@state()
|
||||
private isScheduleDisabled?: boolean;
|
||||
private scheduleTime: { hour: number; minute: number; period: "AM" | "PM" } =
|
||||
{
|
||||
hour: new Date().getHours() % 12 || 12,
|
||||
minute: 0,
|
||||
period: new Date().getHours() > 11 ? "PM" : "AM",
|
||||
};
|
||||
|
||||
@state()
|
||||
private schedulePeriod: "AM" | "PM" = initialPeriod;
|
||||
private get isScheduleDisabled(): boolean {
|
||||
return !this.scheduleInterval;
|
||||
}
|
||||
|
||||
private get timeZoneShortName() {
|
||||
return getLocaleTimeZone();
|
||||
firstUpdated() {
|
||||
this.setInitialValues();
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO consolidate with new
|
||||
const hours = Array.from({ length: 12 }).map((x, i) => ({
|
||||
value: i + 1,
|
||||
label: `${i + 1}`,
|
||||
}));
|
||||
const minutes = Array.from({ length: 60 }).map((x, i) => ({
|
||||
value: i,
|
||||
label: `${i}`.padStart(2, "0"),
|
||||
}));
|
||||
|
||||
const getInitialScheduleInterval = (schedule: string) => {
|
||||
const [minute, hour, dayofMonth, month, dayOfWeek] = schedule.split(" ");
|
||||
if (dayofMonth === "*") {
|
||||
if (dayOfWeek === "*") {
|
||||
return "daily";
|
||||
}
|
||||
return "weekly";
|
||||
}
|
||||
return "monthly";
|
||||
};
|
||||
const scheduleIntervalsMap = {
|
||||
daily: `0 ${nowHour} * * *`,
|
||||
weekly: `0 ${nowHour} * * ${new Date().getDay()}`,
|
||||
monthly: `0 ${nowHour} ${new Date().getDate()} * *`,
|
||||
};
|
||||
const initialInterval = this.schedule
|
||||
? getInitialScheduleInterval(this.schedule)
|
||||
: "weekly";
|
||||
const nextSchedule =
|
||||
this.editedSchedule || scheduleIntervalsMap[initialInterval];
|
||||
const utcSchedule = this.getUTCSchedule();
|
||||
|
||||
return html`
|
||||
<sl-form @sl-submit=${this.onSubmit}>
|
||||
@ -87,23 +77,12 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
<sl-select
|
||||
name="scheduleInterval"
|
||||
label=${msg("Recurring crawls")}
|
||||
value=${initialInterval}
|
||||
value=${this.scheduleInterval}
|
||||
hoist
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
@sl-select=${(e: any) => {
|
||||
if (e.target.value) {
|
||||
this.isScheduleDisabled = false;
|
||||
this.editedSchedule = `${nextSchedule
|
||||
.split(" ")
|
||||
.slice(0, 2)
|
||||
.join(" ")} ${(scheduleIntervalsMap as any)[e.target.value]
|
||||
.split(" ")
|
||||
.slice(2)
|
||||
.join(" ")}`;
|
||||
} else {
|
||||
this.isScheduleDisabled = true;
|
||||
}
|
||||
this.scheduleInterval = e.target.value;
|
||||
}}
|
||||
>
|
||||
<sl-menu-item value="">${msg("None")}</sl-menu-item>
|
||||
@ -120,20 +99,16 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
<sl-select
|
||||
class="grow"
|
||||
name="scheduleHour"
|
||||
value=${initialHours}
|
||||
value=${this.scheduleTime.hour}
|
||||
?disabled=${this.isScheduleDisabled}
|
||||
hoist
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
@sl-select=${(e: any) => {
|
||||
const hour = +e.target.value;
|
||||
const period = this.schedulePeriod;
|
||||
|
||||
this.setScheduleHour({
|
||||
hour,
|
||||
period,
|
||||
schedule: nextSchedule,
|
||||
});
|
||||
this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
hour: +e.target.value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
${hours.map(
|
||||
@ -145,16 +120,16 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
<sl-select
|
||||
class="grow"
|
||||
name="scheduleMinute"
|
||||
value="0"
|
||||
value=${this.scheduleTime.minute}
|
||||
?disabled=${this.isScheduleDisabled}
|
||||
hoist
|
||||
@sl-hide=${this.stopProp}
|
||||
@sl-after-hide=${this.stopProp}
|
||||
@sl-select=${(e: any) =>
|
||||
(this.editedSchedule = `${e.target.value} ${nextSchedule
|
||||
.split(" ")
|
||||
.slice(1)
|
||||
.join(" ")}`)}
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
minute: +e.target.value,
|
||||
})}
|
||||
>
|
||||
${minutes.map(
|
||||
({ value, label }) =>
|
||||
@ -165,66 +140,52 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
<input
|
||||
name="schedulePeriod"
|
||||
type="hidden"
|
||||
value=${this.schedulePeriod}
|
||||
value=${this.scheduleTime.period}
|
||||
/>
|
||||
<sl-button-group>
|
||||
<sl-button
|
||||
type=${this.schedulePeriod === "AM" ? "neutral" : "default"}
|
||||
aria-selected=${this.schedulePeriod === "AM"}
|
||||
type=${this.scheduleTime.period === "AM"
|
||||
? "neutral"
|
||||
: "default"}
|
||||
aria-selected=${this.scheduleTime.period === "AM"}
|
||||
?disabled=${this.isScheduleDisabled}
|
||||
@click=${(e: any) => {
|
||||
const hour = +e.target
|
||||
.closest("sl-form")
|
||||
.querySelector('sl-select[name="scheduleHour"]').value;
|
||||
const period = "AM";
|
||||
|
||||
this.schedulePeriod = period;
|
||||
this.setScheduleHour({
|
||||
hour,
|
||||
period,
|
||||
schedule: nextSchedule,
|
||||
});
|
||||
}}
|
||||
@click=${() =>
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
period: "AM",
|
||||
})}
|
||||
>${msg("AM", { desc: "Time AM/PM" })}</sl-button
|
||||
>
|
||||
<sl-button
|
||||
type=${this.schedulePeriod === "PM" ? "neutral" : "default"}
|
||||
aria-selected=${this.schedulePeriod === "PM"}
|
||||
type=${this.scheduleTime.period === "PM"
|
||||
? "neutral"
|
||||
: "default"}
|
||||
aria-selected=${this.scheduleTime.period === "PM"}
|
||||
?disabled=${this.isScheduleDisabled}
|
||||
@click=${(e: any) => {
|
||||
const hour = +e.target
|
||||
.closest("sl-form")
|
||||
.querySelector('sl-select[name="scheduleHour"]').value;
|
||||
const period = "PM";
|
||||
|
||||
this.schedulePeriod = period;
|
||||
this.setScheduleHour({
|
||||
hour,
|
||||
period,
|
||||
schedule: nextSchedule,
|
||||
});
|
||||
}}
|
||||
@click=${() =>
|
||||
(this.scheduleTime = {
|
||||
...this.scheduleTime,
|
||||
period: "PM",
|
||||
})}
|
||||
>${msg("PM", { desc: "Time AM/PM" })}</sl-button
|
||||
>
|
||||
</sl-button-group>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="mt-5">
|
||||
<div class="mt-5 bg-neutral-50 rounded p-3 text-sm text-neutral-800">
|
||||
${this.isScheduleDisabled
|
||||
? msg(html`<span class="font-medium"
|
||||
>Crawls will not repeat.</span
|
||||
>`)
|
||||
: msg(
|
||||
html`<span class="font-medium">New schedule will be:</span
|
||||
><br />
|
||||
<span class="text-0-600"
|
||||
>${cronstrue.toString(nextSchedule, {
|
||||
verbose: true,
|
||||
})}
|
||||
(in ${this.timeZoneShortName} time zone)</span
|
||||
>`
|
||||
)}
|
||||
? html`<span class="font-medium"
|
||||
>${msg("Crawls will not repeat.")}</span
|
||||
>`
|
||||
: html`
|
||||
<p>${msg(str`Schedule: ${humanizeSchedule(utcSchedule)}.`)}</p>
|
||||
<p>
|
||||
${msg(
|
||||
str`Next scheduled run: ${humanizeNextDate(utcSchedule)}.`
|
||||
)}
|
||||
</p>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<div class="mt-5${this.cancelable ? " text-right" : ""}">
|
||||
@ -256,34 +217,6 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
this.dispatchEvent(new CustomEvent("submit", event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set correct local hour in schedule in 24-hr format
|
||||
**/
|
||||
private setScheduleHour({
|
||||
hour,
|
||||
period,
|
||||
schedule,
|
||||
}: {
|
||||
hour: number;
|
||||
period: "AM" | "PM";
|
||||
schedule: string;
|
||||
}) {
|
||||
// Convert 12-hr to 24-hr time
|
||||
let periodOffset = 0;
|
||||
|
||||
if (hour === 12) {
|
||||
if (period === "AM") {
|
||||
periodOffset = -12;
|
||||
}
|
||||
} else if (period === "PM") {
|
||||
periodOffset = 12;
|
||||
}
|
||||
|
||||
this.editedSchedule = `${schedule.split(" ")[0]} ${
|
||||
hour + periodOffset
|
||||
} ${schedule.split(" ").slice(2).join(" ")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop propgation of sl-select events.
|
||||
* Prevents bug where sl-dialog closes when dropdown closes
|
||||
@ -292,6 +225,29 @@ export class CrawlTemplatesScheduler extends LiteElement {
|
||||
private stopProp(e: CustomEvent) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private setInitialValues() {
|
||||
if (this.schedule) {
|
||||
const nextDate = parseCron.nextDate(this.schedule)!;
|
||||
const hours = nextDate.getHours();
|
||||
|
||||
this.scheduleTime = {
|
||||
hour: hours % 12,
|
||||
minute: nextDate.getMinutes(),
|
||||
period: hours > 11 ? "PM" : "AM",
|
||||
};
|
||||
this.scheduleInterval = getScheduleInterval(this.schedule);
|
||||
}
|
||||
}
|
||||
|
||||
private getUTCSchedule(): string {
|
||||
if (!this.scheduleInterval) return "";
|
||||
|
||||
return getUTCSchedule({
|
||||
interval: this.scheduleInterval,
|
||||
...this.scheduleTime,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("btrix-crawl-scheduler", CrawlTemplatesScheduler);
|
||||
|
||||
@ -2,14 +2,13 @@ import type { HTMLTemplateResult } from "lit";
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronstrue from "cronstrue"; // TODO localize
|
||||
import { parse as yamlToJson, stringify as jsonToYaml } from "yaml";
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import type { InitialCrawlTemplate } from "./crawl-templates-new";
|
||||
import type { CrawlTemplate, CrawlConfig } from "./types";
|
||||
import { getUTCSchedule } from "./utils";
|
||||
import { getUTCSchedule, humanizeSchedule } from "../../utils/cron";
|
||||
import "../../components/crawl-scheduler";
|
||||
|
||||
const SEED_URLS_MAX = 3;
|
||||
@ -707,16 +706,7 @@ export class CrawlTemplatesDetail extends LiteElement {
|
||||
${this.crawlTemplate
|
||||
? html`
|
||||
${this.crawlTemplate.schedule
|
||||
? // TODO localize
|
||||
// NOTE human-readable string is in UTC, limitation of library
|
||||
// currently being used.
|
||||
// https://github.com/bradymholt/cRonstrue/issues/94
|
||||
html`<span
|
||||
>${cronstrue.toString(this.crawlTemplate.schedule, {
|
||||
verbose: true,
|
||||
})}
|
||||
(in UTC time zone)</span
|
||||
>`
|
||||
? humanizeSchedule(this.crawlTemplate.schedule)
|
||||
: html`<span class="text-0-400">${msg("None")}</span>`}
|
||||
`
|
||||
: html`<sl-skeleton></sl-skeleton>`}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { HTMLTemplateResult } from "lit";
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronParser from "cron-parser";
|
||||
import { parseCron } from "@cheap-glitch/mi-cron";
|
||||
import debounce from "lodash/fp/debounce";
|
||||
import flow from "lodash/fp/flow";
|
||||
import map from "lodash/fp/map";
|
||||
@ -13,7 +13,11 @@ import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import type { InitialCrawlTemplate } from "./crawl-templates-new";
|
||||
import type { CrawlTemplate } from "./types";
|
||||
import { getUTCSchedule } from "./utils";
|
||||
import {
|
||||
getUTCSchedule,
|
||||
humanizeNextDate,
|
||||
humanizeSchedule,
|
||||
} from "../../utils/cron";
|
||||
import "../../components/crawl-scheduler";
|
||||
|
||||
type RunningCrawlsMap = {
|
||||
@ -359,7 +363,6 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
year="2-digit"
|
||||
hour="numeric"
|
||||
minute="numeric"
|
||||
time-zone-name="short"
|
||||
></sl-format-date>
|
||||
</a>
|
||||
</sl-tooltip>`
|
||||
@ -376,27 +379,21 @@ export class CrawlTemplatesList extends LiteElement {
|
||||
<div>
|
||||
${t.schedule
|
||||
? html`
|
||||
<sl-tooltip content=${msg("Next scheduled crawl")}>
|
||||
<sl-tooltip
|
||||
content=${msg(
|
||||
str`Next scheduled crawl: ${humanizeNextDate(t.schedule)}`
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
<sl-icon
|
||||
class="inline-block align-middle mr-1"
|
||||
name="clock-history"
|
||||
></sl-icon
|
||||
><sl-format-date
|
||||
class="inline-block align-middle text-0-600"
|
||||
date="${cronParser
|
||||
.parseExpression(t.schedule, {
|
||||
utc: true,
|
||||
})
|
||||
.next()
|
||||
.toString()}"
|
||||
month="2-digit"
|
||||
day="2-digit"
|
||||
year="2-digit"
|
||||
hour="numeric"
|
||||
minute="numeric"
|
||||
time-zone-name="short"
|
||||
></sl-format-date>
|
||||
><span class="inline-block align-middle text-0-600"
|
||||
>${humanizeSchedule(t.schedule, {
|
||||
length: "short",
|
||||
})}</span
|
||||
>
|
||||
</span>
|
||||
</sl-tooltip>
|
||||
`
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { state, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { msg, localized, str } from "@lit/localize";
|
||||
import cronParser from "cron-parser";
|
||||
import { parse as yamlToJson, stringify as jsonToYaml } from "yaml";
|
||||
|
||||
import type { AuthState } from "../../utils/AuthService";
|
||||
import LiteElement, { html } from "../../utils/LiteElement";
|
||||
import { getLocaleTimeZone } from "../../utils/localization";
|
||||
import { ScheduleInterval, humanizeNextDate } from "../../utils/cron";
|
||||
import type { CrawlConfig, Profile } from "./types";
|
||||
import { getUTCSchedule } from "./utils";
|
||||
import { getUTCSchedule } from "../../utils/cron";
|
||||
|
||||
type NewCrawlTemplate = {
|
||||
id?: string;
|
||||
@ -65,7 +64,7 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
private isRunNow: boolean = initialValues.runNow;
|
||||
|
||||
@state()
|
||||
private scheduleInterval: "" | "daily" | "weekly" | "monthly" = "";
|
||||
private scheduleInterval: ScheduleInterval | "" = "";
|
||||
|
||||
/** Schedule local time */
|
||||
@state()
|
||||
@ -92,35 +91,10 @@ export class CrawlTemplatesNew extends LiteElement {
|
||||
@state()
|
||||
private serverError?: string;
|
||||
|
||||
private get timeZone() {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
|
||||
private get timeZoneShortName() {
|
||||
return getLocaleTimeZone();
|
||||
}
|
||||
|
||||
private get formattededNextCrawlDate() {
|
||||
const utcSchedule = this.getUTCSchedule();
|
||||
|
||||
return this.scheduleInterval
|
||||
? html`<sl-format-date
|
||||
date="${cronParser
|
||||
.parseExpression(utcSchedule, {
|
||||
utc: true,
|
||||
})
|
||||
.next()
|
||||
.toString()}"
|
||||
weekday="long"
|
||||
month="long"
|
||||
day="numeric"
|
||||
year="numeric"
|
||||
hour="numeric"
|
||||
minute="numeric"
|
||||
time-zone-name="short"
|
||||
time-zone=${this.timeZone}
|
||||
></sl-format-date>`
|
||||
: undefined;
|
||||
return this.scheduleInterval ? humanizeNextDate(utcSchedule) : undefined;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Get schedule as UTC cron job expression
|
||||
* https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#cron-schedule-syntax
|
||||
**/
|
||||
export function getUTCSchedule({
|
||||
interval,
|
||||
minute,
|
||||
hour,
|
||||
period,
|
||||
}: {
|
||||
interval: "daily" | "weekly" | "monthly";
|
||||
minute: number | string;
|
||||
hour: number | string;
|
||||
period: "AM" | "PM";
|
||||
}): string {
|
||||
const localDate = new Date();
|
||||
|
||||
// Convert 12-hr to 24-hr time
|
||||
let periodOffset = 0;
|
||||
|
||||
if (hour === 12) {
|
||||
if (period === "AM") {
|
||||
periodOffset = -12;
|
||||
}
|
||||
} else if (period === "PM") {
|
||||
periodOffset = 12;
|
||||
}
|
||||
|
||||
localDate.setHours(+hour + periodOffset);
|
||||
localDate.setMinutes(+minute);
|
||||
const dayOfMonth = interval === "monthly" ? localDate.getUTCDate() : "*";
|
||||
const dayOfWeek = interval === "weekly" ? localDate.getUTCDay() : "*";
|
||||
const month = "*";
|
||||
|
||||
const schedule = `${localDate.getUTCMinutes()} ${localDate.getUTCHours()} ${dayOfMonth} ${month} ${dayOfWeek}`;
|
||||
|
||||
return schedule;
|
||||
}
|
||||
145
frontend/src/utils/cron.ts
Normal file
145
frontend/src/utils/cron.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { parseCron } from "@cheap-glitch/mi-cron";
|
||||
import { msg, str } from "@lit/localize";
|
||||
|
||||
export type ScheduleInterval = "daily" | "weekly" | "monthly";
|
||||
|
||||
/**
|
||||
* Parse interval from cron expression
|
||||
**/
|
||||
export function getScheduleInterval(
|
||||
schedule: string
|
||||
): "daily" | "weekly" | "monthly" {
|
||||
const [minute, hour, dayofMonth, month, dayOfWeek] = schedule.split(" ");
|
||||
if (dayofMonth === "*") {
|
||||
if (dayOfWeek === "*") {
|
||||
return "daily";
|
||||
}
|
||||
return "weekly";
|
||||
}
|
||||
return "monthly";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-friendly date from cron expression
|
||||
* Example: "Every day at 9:30 AM CDT"
|
||||
**/
|
||||
export function humanizeNextDate(schedule: string): string {
|
||||
const nextDate = parseCron.nextDate(schedule);
|
||||
|
||||
if (!nextDate) return "";
|
||||
|
||||
return nextDate.toLocaleString(undefined, {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
timeZoneName: "short",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human-friendly schedule from cron expression
|
||||
* Example: "Every day at 9:30 AM CDT"
|
||||
**/
|
||||
export function humanizeSchedule(
|
||||
schedule: string,
|
||||
options: { length?: "short" } = {}
|
||||
): string {
|
||||
const interval = getScheduleInterval(schedule);
|
||||
const { days } = parseCron(schedule)!;
|
||||
const nextDate = parseCron.nextDate(schedule)!;
|
||||
const formattedWeekDay = nextDate.toLocaleString(undefined, {
|
||||
weekday: "long",
|
||||
});
|
||||
|
||||
let intervalMsg: any = "";
|
||||
|
||||
if (options.length === "short") {
|
||||
const formattedTime = nextDate.toLocaleString(undefined, {
|
||||
minute: "numeric",
|
||||
hour: "numeric",
|
||||
});
|
||||
|
||||
switch (interval) {
|
||||
case "daily":
|
||||
intervalMsg = msg(str`${formattedTime} every day`);
|
||||
break;
|
||||
case "weekly":
|
||||
intervalMsg = msg(str`Every ${formattedWeekDay}`);
|
||||
break;
|
||||
case "monthly":
|
||||
intervalMsg = msg(str`Day ${days[0]} of every month`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const formattedTime = nextDate.toLocaleString(undefined, {
|
||||
minute: "numeric",
|
||||
hour: "numeric",
|
||||
timeZoneName: "short",
|
||||
});
|
||||
|
||||
switch (interval) {
|
||||
case "daily":
|
||||
intervalMsg = msg(str`Every day at ${formattedTime}`);
|
||||
break;
|
||||
case "weekly":
|
||||
intervalMsg = msg(
|
||||
str`Every ${formattedWeekDay}
|
||||
at ${formattedTime}`
|
||||
);
|
||||
break;
|
||||
case "monthly":
|
||||
intervalMsg = msg(
|
||||
str`On day ${days[0]} of the month at ${formattedTime}`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return intervalMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schedule as UTC cron job expression
|
||||
* https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#cron-schedule-syntax
|
||||
**/
|
||||
export function getUTCSchedule({
|
||||
interval,
|
||||
minute,
|
||||
hour,
|
||||
period,
|
||||
}: {
|
||||
interval: ScheduleInterval;
|
||||
minute: number | string;
|
||||
hour: number | string;
|
||||
period: "AM" | "PM";
|
||||
}): string {
|
||||
const localDate = new Date();
|
||||
|
||||
// Convert 12-hr to 24-hr time
|
||||
let periodOffset = 0;
|
||||
|
||||
if (hour === 12) {
|
||||
if (period === "AM") {
|
||||
periodOffset = -12;
|
||||
}
|
||||
} else if (period === "PM") {
|
||||
periodOffset = 12;
|
||||
}
|
||||
|
||||
localDate.setHours(+hour + periodOffset);
|
||||
localDate.setMinutes(+minute);
|
||||
const dayOfMonth = interval === "monthly" ? localDate.getUTCDate() : "*";
|
||||
const dayOfWeek = interval === "weekly" ? localDate.getUTCDay() : "*";
|
||||
const month = "*";
|
||||
|
||||
const schedule = `${localDate.getUTCMinutes()} ${localDate.getUTCHours()} ${dayOfMonth} ${month} ${dayOfWeek}`;
|
||||
|
||||
return schedule;
|
||||
}
|
||||
@ -39,6 +39,11 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@cheap-glitch/mi-cron@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@cheap-glitch/mi-cron/-/mi-cron-1.0.1.tgz#111f4ce746c269aedf74533ac806881763a68f99"
|
||||
integrity sha512-kxl7vhg+SUgyHRn22qVbR9MfSm5CzdlYZDJTbGemqFFi/Jmno/hdoQIvBIPoqFY9dcPyxzOUNRRFn6x88UQMpw==
|
||||
|
||||
"@discoveryjs/json-ext@^0.5.0":
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
|
||||
@ -1774,18 +1779,6 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
cron-parser@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.2.1.tgz#b43205d05ccd5c93b097dae64f3bd811f5993af3"
|
||||
integrity sha512-5sJBwDYyCp+0vU5b7POl8zLWfgV5fOHxlc45FWoWdHecGC7MQHCjx0CHivCMRnGFovghKhhyYM+Zm9DcY5qcHg==
|
||||
dependencies:
|
||||
luxon "^1.28.0"
|
||||
|
||||
cronstrue@^1.123.0:
|
||||
version "1.123.0"
|
||||
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-1.123.0.tgz#de622dd8ea07981790f488d3f89a25e6e728ac8b"
|
||||
integrity sha512-hVu9yNYRYr+jj5KET1p7FaBxFwtCHM1ByffP9lZ6yJ6p53u4VEmzH8117v9PUydxWNzc8Eq+sCZEzsKcB3ckiA==
|
||||
|
||||
cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@ -3558,11 +3551,6 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
luxon@^1.28.0:
|
||||
version "1.28.0"
|
||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
|
||||
integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
|
||||
|
||||
make-dir@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user