diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index b4222412..42539e70 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -9,6 +9,7 @@
icon.jpg
true
9.0.2
+ [10.0.0,11.0.0)
latest
diff --git a/src/TickerQ.Dashboard/wwwroot/src/components/crontickerComponents/CronOccurrenceDialog.vue b/src/TickerQ.Dashboard/wwwroot/src/components/crontickerComponents/CronOccurrenceDialog.vue
index 0a181fd6..53a023ac 100644
--- a/src/TickerQ.Dashboard/wwwroot/src/components/crontickerComponents/CronOccurrenceDialog.vue
+++ b/src/TickerQ.Dashboard/wwwroot/src/components/crontickerComponents/CronOccurrenceDialog.vue
@@ -9,8 +9,7 @@ import { methodName, type TickerNotificationHubType } from '@/hub/tickerNotifica
import type { GetCronTickerOccurrenceResponse } from '@/http/services/types/cronTickerOccurrenceService.types'
import { ConfirmDialogProps } from '@/components/common/ConfirmDialog.vue'
import PaginationFooter from '@/components/PaginationFooter.vue'
-import { formatTime } from '@/utilities/dateTimeParser'
-import { format } from 'timeago.js'
+import { formatTime, formatTimeAgo } from '@/utilities/dateTimeParser'
const confirmDialog = useDialog<{ data: string }>().withComponent(
() => import('@/components/common/ConfirmDialog.vue'),
@@ -100,7 +99,7 @@ const addHubListeners = async () => {
...currentItem,
...val,
status: Status[val.status as any],
- executedAt: `${format(val.executedAt)} (took ${formatTime(val.elapsedTime as number, true)})`,
+ executedAt: `${formatTimeAgo(val.executedAt)} (took ${formatTime(val.elapsedTime as number, true)})`,
retryIntervals: currentItem.retryIntervals,
lockedAt: currentItem.lockedAt, // Preserve existing lockedAt
lockHolder: currentItem.lockHolder,
diff --git a/src/TickerQ.Dashboard/wwwroot/src/http/services/cronTickerOccurrenceService.ts b/src/TickerQ.Dashboard/wwwroot/src/http/services/cronTickerOccurrenceService.ts
index cab9e972..471364d2 100644
--- a/src/TickerQ.Dashboard/wwwroot/src/http/services/cronTickerOccurrenceService.ts
+++ b/src/TickerQ.Dashboard/wwwroot/src/http/services/cronTickerOccurrenceService.ts
@@ -1,9 +1,8 @@
-import { formatDate, formatTime } from '@/utilities/dateTimeParser';
+import { formatDate, formatTime, formatTimeAgo } from '@/utilities/dateTimeParser';
import { useBaseHttpService } from '../base/baseHttpService';
import { Status } from './types/base/baseHttpResponse.types';
import { GetCronTickerOccurrenceGraphDataRequest, GetCronTickerOccurrenceGraphDataResponse, GetCronTickerOccurrenceRequest, GetCronTickerOccurrenceResponse } from './types/cronTickerOccurrenceService.types';
-import { format} from 'timeago.js';
import { nameof } from '@/utilities/nameof';
import { useTimeZoneStore } from '@/stores/timeZoneStore';
@@ -23,14 +22,12 @@ const getByCronTickerId = () => {
}
// Safely set status with null check
- if (response.status !== undefined && response.status !== null) {
+ if (response.status != null) {
response.status = Status[response.status as any];
}
- if (response.executedAt != null || response.executedAt != undefined) {
- // Ensure the datetime is treated as UTC by adding 'Z' if missing
- const utcExecutedAt = response.executedAt.endsWith('Z') ? response.executedAt : response.executedAt + 'Z';
- response.executedAt = `${format(utcExecutedAt)} (took ${formatTime(response.elapsedTime as number, true)})`;
+ if (response.executedAt != null) {
+ response.executedAt = `${formatTimeAgo(response.executedAt)} (took ${formatTime(response.elapsedTime as number, true)})`;
}
const utcExecutionTime = response.executionTime.endsWith('Z') ? response.executionTime : response.executionTime + 'Z';
@@ -75,17 +72,15 @@ const getByCronTickerIdPaginated = () => {
if (!item) return item;
// Safely set status with null check and ensure it's always a string
- if (item.status !== undefined && item.status !== null) {
+ if (item.status != null) {
const statusValue = Status[item.status as any];
item.status = statusValue !== undefined ? statusValue : String(item.status);
} else {
item.status = 'Unknown';
}
- if (item.executedAt != null || item.executedAt != undefined) {
- // Ensure the datetime is treated as UTC by adding 'Z' if missing
- const utcExecutedAt = item.executedAt.endsWith('Z') ? item.executedAt : item.executedAt + 'Z';
- item.executedAt = `${format(utcExecutedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
+ if (item.executedAt != null) {
+ item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
}
const utcExecutionTime = item.executionTime.endsWith('Z') ? item.executionTime : item.executionTime + 'Z';
diff --git a/src/TickerQ.Dashboard/wwwroot/src/http/services/timeTickerService.ts b/src/TickerQ.Dashboard/wwwroot/src/http/services/timeTickerService.ts
index 644a90d3..5a2ab9e6 100644
--- a/src/TickerQ.Dashboard/wwwroot/src/http/services/timeTickerService.ts
+++ b/src/TickerQ.Dashboard/wwwroot/src/http/services/timeTickerService.ts
@@ -1,5 +1,5 @@
-import { formatDate, formatTime } from '@/utilities/dateTimeParser';
+import { formatDate, formatTime, formatTimeAgo } from '@/utilities/dateTimeParser';
import { useBaseHttpService } from '../base/baseHttpService';
import { Status } from './types/base/baseHttpResponse.types';
import {
@@ -11,7 +11,6 @@ import {
UpdateTimeTickerRequest
} from './types/timeTickerService.types'
import { nameof } from '@/utilities/nameof';
-import { format} from 'timeago.js';
import { useFunctionNameStore } from '@/stores/functionNames';
import { useTimeZoneStore } from '@/stores/timeZoneStore';
@@ -36,12 +35,12 @@ const getTimeTickers = () => {
// Recursive function to process item and its children
const processItem = (item: GetTimeTickerResponse): GetTimeTickerResponse => {
// Safely set status with null check
- if (item.status !== undefined && item.status !== null) {
+ if (item.status != null) {
item.status = Status[item.status as any];
}
- if (item.executedAt != null || item.executedAt != undefined)
- item.executedAt = `${format(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
+ if (item.executedAt != null)
+ item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
item.executionTimeFormatted = formatDate(item.executionTime, true, timeZoneStore.effectiveTimeZone);
item.requestType = functionNamesStore.getNamespaceOrNull(item.function) ?? '';
@@ -108,12 +107,12 @@ const getTimeTickersPaginated = () => {
if (response && response.items && Array.isArray(response.items)) {
response.items = response.items.map((item: GetTimeTickerResponse) => {
const processItem = (item: GetTimeTickerResponse): GetTimeTickerResponse => {
- if (item.status !== undefined && item.status !== null) {
+ if (item.status != null) {
item.status = Status[item.status as any];
}
- if (item.executedAt != null || item.executedAt != undefined)
- item.executedAt = `${format(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
+ if (item.executedAt != null)
+ item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
item.executionTimeFormatted = formatDate(item.executionTime, true, timeZoneStore.effectiveTimeZone);
item.requestType = functionNamesStore.getNamespaceOrNull(item.function) ?? '';
diff --git a/src/TickerQ.Dashboard/wwwroot/src/utilities/dateTimeParser.ts b/src/TickerQ.Dashboard/wwwroot/src/utilities/dateTimeParser.ts
index 6102b001..917bde4f 100644
--- a/src/TickerQ.Dashboard/wwwroot/src/utilities/dateTimeParser.ts
+++ b/src/TickerQ.Dashboard/wwwroot/src/utilities/dateTimeParser.ts
@@ -1,3 +1,4 @@
+import { format as timeago } from 'timeago.js';
export function formatDate(
utcDateString: string,
@@ -126,3 +127,14 @@ export function formatFromUtcToLocal(utcDateString: string): string {
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`
}
+
+export function formatTimeAgo(date: string | Date): string {
+ // Front-end often passes dates as strings straight up from JSON payloads.
+ // All dates on back-end are UTC but dates loaded by EF have DateTimeKind.Unspecified by default,
+ // which is serialized to JSON without any offset suffix.
+ // We have to specify them as UTC so that they're not parsed as local time by JS.
+ if (typeof date === 'string' && !date.endsWith('Z')) {
+ date = date + 'Z'
+ }
+ return timeago(date)
+}
\ No newline at end of file