Skip to content

Commit d24022f

Browse files
authored
Fix #497 (#503)
1 parent 52039f1 commit d24022f

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

src/TickerQ.Dashboard/wwwroot/src/components/crontickerComponents/CronOccurrenceDialog.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { methodName, type TickerNotificationHubType } from '@/hub/tickerNotifica
99
import type { GetCronTickerOccurrenceResponse } from '@/http/services/types/cronTickerOccurrenceService.types'
1010
import { ConfirmDialogProps } from '@/components/common/ConfirmDialog.vue'
1111
import PaginationFooter from '@/components/PaginationFooter.vue'
12-
import { formatTime } from '@/utilities/dateTimeParser'
13-
import { format } from 'timeago.js'
12+
import { formatTime, formatTimeAgo } from '@/utilities/dateTimeParser'
1413
1514
const confirmDialog = useDialog<{ data: string }>().withComponent(
1615
() => import('@/components/common/ConfirmDialog.vue'),
@@ -100,7 +99,7 @@ const addHubListeners = async () => {
10099
...currentItem,
101100
...val,
102101
status: Status[val.status as any],
103-
executedAt: `${format(val.executedAt)} (took ${formatTime(val.elapsedTime as number, true)})`,
102+
executedAt: `${formatTimeAgo(val.executedAt)} (took ${formatTime(val.elapsedTime as number, true)})`,
104103
retryIntervals: currentItem.retryIntervals,
105104
lockedAt: currentItem.lockedAt, // Preserve existing lockedAt
106105
lockHolder: currentItem.lockHolder,

src/TickerQ.Dashboard/wwwroot/src/http/services/cronTickerOccurrenceService.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11

2-
import { formatDate, formatTime } from '@/utilities/dateTimeParser';
2+
import { formatDate, formatTime, formatTimeAgo } from '@/utilities/dateTimeParser';
33
import { useBaseHttpService } from '../base/baseHttpService';
44
import { Status } from './types/base/baseHttpResponse.types';
55
import { GetCronTickerOccurrenceGraphDataRequest, GetCronTickerOccurrenceGraphDataResponse, GetCronTickerOccurrenceRequest, GetCronTickerOccurrenceResponse } from './types/cronTickerOccurrenceService.types';
6-
import { format} from 'timeago.js';
76
import { nameof } from '@/utilities/nameof';
87
import { useTimeZoneStore } from '@/stores/timeZoneStore';
98

@@ -23,14 +22,12 @@ const getByCronTickerId = () => {
2322
}
2423

2524
// Safely set status with null check
26-
if (response.status !== undefined && response.status !== null) {
25+
if (response.status != null) {
2726
response.status = Status[response.status as any];
2827
}
2928

30-
if (response.executedAt != null || response.executedAt != undefined) {
31-
// Ensure the datetime is treated as UTC by adding 'Z' if missing
32-
const utcExecutedAt = response.executedAt.endsWith('Z') ? response.executedAt : response.executedAt + 'Z';
33-
response.executedAt = `${format(utcExecutedAt)} (took ${formatTime(response.elapsedTime as number, true)})`;
29+
if (response.executedAt != null) {
30+
response.executedAt = `${formatTimeAgo(response.executedAt)} (took ${formatTime(response.elapsedTime as number, true)})`;
3431
}
3532

3633
const utcExecutionTime = response.executionTime.endsWith('Z') ? response.executionTime : response.executionTime + 'Z';
@@ -75,17 +72,15 @@ const getByCronTickerIdPaginated = () => {
7572
if (!item) return item;
7673

7774
// Safely set status with null check and ensure it's always a string
78-
if (item.status !== undefined && item.status !== null) {
75+
if (item.status != null) {
7976
const statusValue = Status[item.status as any];
8077
item.status = statusValue !== undefined ? statusValue : String(item.status);
8178
} else {
8279
item.status = 'Unknown';
8380
}
8481

85-
if (item.executedAt != null || item.executedAt != undefined) {
86-
// Ensure the datetime is treated as UTC by adding 'Z' if missing
87-
const utcExecutedAt = item.executedAt.endsWith('Z') ? item.executedAt : item.executedAt + 'Z';
88-
item.executedAt = `${format(utcExecutedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
82+
if (item.executedAt != null) {
83+
item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
8984
}
9085

9186
const utcExecutionTime = item.executionTime.endsWith('Z') ? item.executionTime : item.executionTime + 'Z';

src/TickerQ.Dashboard/wwwroot/src/http/services/timeTickerService.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
import { formatDate, formatTime } from '@/utilities/dateTimeParser';
2+
import { formatDate, formatTime, formatTimeAgo } from '@/utilities/dateTimeParser';
33
import { useBaseHttpService } from '../base/baseHttpService';
44
import { Status } from './types/base/baseHttpResponse.types';
55
import {
@@ -11,7 +11,6 @@ import {
1111
UpdateTimeTickerRequest
1212
} from './types/timeTickerService.types'
1313
import { nameof } from '@/utilities/nameof';
14-
import { format} from 'timeago.js';
1514
import { useFunctionNameStore } from '@/stores/functionNames';
1615
import { useTimeZoneStore } from '@/stores/timeZoneStore';
1716

@@ -36,12 +35,12 @@ const getTimeTickers = () => {
3635
// Recursive function to process item and its children
3736
const processItem = (item: GetTimeTickerResponse): GetTimeTickerResponse => {
3837
// Safely set status with null check
39-
if (item.status !== undefined && item.status !== null) {
38+
if (item.status != null) {
4039
item.status = Status[item.status as any];
4140
}
4241

43-
if (item.executedAt != null || item.executedAt != undefined)
44-
item.executedAt = `${format(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
42+
if (item.executedAt != null)
43+
item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
4544

4645
item.executionTimeFormatted = formatDate(item.executionTime, true, timeZoneStore.effectiveTimeZone);
4746
item.requestType = functionNamesStore.getNamespaceOrNull(item.function) ?? '';
@@ -108,12 +107,12 @@ const getTimeTickersPaginated = () => {
108107
if (response && response.items && Array.isArray(response.items)) {
109108
response.items = response.items.map((item: GetTimeTickerResponse) => {
110109
const processItem = (item: GetTimeTickerResponse): GetTimeTickerResponse => {
111-
if (item.status !== undefined && item.status !== null) {
110+
if (item.status != null) {
112111
item.status = Status[item.status as any];
113112
}
114113

115-
if (item.executedAt != null || item.executedAt != undefined)
116-
item.executedAt = `${format(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
114+
if (item.executedAt != null)
115+
item.executedAt = `${formatTimeAgo(item.executedAt)} (took ${formatTime(item.elapsedTime as number, true)})`;
117116

118117
item.executionTimeFormatted = formatDate(item.executionTime, true, timeZoneStore.effectiveTimeZone);
119118
item.requestType = functionNamesStore.getNamespaceOrNull(item.function) ?? '';

src/TickerQ.Dashboard/wwwroot/src/utilities/dateTimeParser.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { format as timeago } from 'timeago.js';
12

23
export function formatDate(
34
utcDateString: string,
@@ -126,3 +127,14 @@ export function formatFromUtcToLocal(utcDateString: string): string {
126127

127128
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`
128129
}
130+
131+
export function formatTimeAgo(date: string | Date): string {
132+
// Front-end often passes dates as strings straight up from JSON payloads.
133+
// All dates on back-end are UTC but dates loaded by EF have DateTimeKind.Unspecified by default,
134+
// which is serialized to JSON without any offset suffix.
135+
// We have to specify them as UTC so that they're not parsed as local time by JS.
136+
if (typeof date === 'string' && !date.endsWith('Z')) {
137+
date = date + 'Z'
138+
}
139+
return timeago(date)
140+
}

0 commit comments

Comments
 (0)