Skip to content

Commit 9e9284a

Browse files
fix: [M3-10698] - Update linode/clone endpoint and label calculations for < 1 hour (#13045)
* fix: [M3-9498] - Update linode/clone endpoint and label calculations for < 1 hour * Add changelogs --------- Co-authored-by: Jaalah Ramos <[email protected]>
1 parent 57a9b1e commit 9e9284a

File tree

5 files changed

+62
-9
lines changed

5 files changed

+62
-9
lines changed

packages/api-v4/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
## [2025-11-04] - v0.152.0
22

33

4+
### Changed:
5+
6+
- Change `/linode/instances/<id>/clone` endpoint to use `v4beta` ([#13045](https://github.com/linode/manager/pull/13045))
7+
48
### Upcoming Features:
59

610
- Add endpoints for `/v4/images/sharegroups/members` and `/v4/images/sharegroups/tokens` ([#12984](https://github.com/linode/manager/pull/12984))

packages/api-v4/src/linodes/actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { RebuildLinodeSchema } from '@linode/validation/lib/linodes.schema';
22

3-
import { API_ROOT } from '../constants';
3+
import { API_ROOT, BETA_API_ROOT } from '../constants';
44
import Request, { setData, setMethod, setURL } from '../request';
55

66
import type {
@@ -185,7 +185,7 @@ export const rescueMetalLinode = (linodeId: number): Promise<{}> =>
185185
export const cloneLinode = (sourceLinodeId: number, data: LinodeCloneData) => {
186186
return Request<Linode>(
187187
setURL(
188-
`${API_ROOT}/linode/instances/${encodeURIComponent(sourceLinodeId)}/clone`,
188+
`${BETA_API_ROOT}/linode/instances/${encodeURIComponent(sourceLinodeId)}/clone`,
189189
),
190190
setMethod('POST'),
191191
setData(data),

packages/manager/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
2323
- IAM Account Delegation Tables sorting & filtering ([#13003](https://github.com/linode/manager/pull/13003))
2424
- IAM - Ensure useEntitiesPermissions does not run for admin users ([#13012](https://github.com/linode/manager/pull/13012))
2525
- IAM Parent/Child: fix spacing and add notification ([#13013](https://github.com/linode/manager/pull/13013))
26-
- Upcoming maintenance "When" shows time until start using start_time or policy‑derived start; shows "X days Y hours" when ≥ 1 day ([#13020](https://github.com/linode/manager/pull/13020))
26+
- Upcoming maintenance "When" shows time until start using start_time or policy‑derived start; shows "X days Y hours" when ≥ 1 day ([#13020](https://github.com/linode/manager/pull/13020), [#13045](https://github.com/linode/manager/pull/13045))
2727
- Add self-service maintenance action in LinodeMaintenanceBanner for power_off_on and include all maintenance types in dev tools preset ([#13024](https://github.com/linode/manager/pull/13024))
2828
- IAM: Linodes without required permissions visible and selectable in Assign/Unassign Linodes selector ([#13030](https://github.com/linode/manager/pull/13030))
2929
- Enhance `enabled` checks for queries ran within `useQueryWithPermissions` ([#13039](https://github.com/linode/manager/pull/13039))

packages/manager/src/features/Account/Maintenance/utilities.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,25 @@ describe('Account Maintenance utilities', () => {
132132
const label = getUpcomingRelativeLabel(m, policies);
133133
expect(label).toBe('in 2 days 16 hours');
134134
});
135+
136+
it('shows exact minutes when under one hour', () => {
137+
const m: AccountMaintenance = {
138+
...baseMaintenance,
139+
// NOW is 12:00Z; start in 37 minutes
140+
start_time: '2025-10-27T12:37:00.000Z',
141+
};
142+
const label = getUpcomingRelativeLabel(m, policies);
143+
expect(label).toBe('in 37 minutes');
144+
});
145+
146+
it('shows seconds when under one minute', () => {
147+
const m: AccountMaintenance = {
148+
...baseMaintenance,
149+
// NOW is 12:00Z; start in 30 seconds
150+
start_time: '2025-10-27T12:00:30.000Z',
151+
};
152+
const label = getUpcomingRelativeLabel(m, policies);
153+
expect(label).toBe('in 30 seconds');
154+
});
135155
});
136156
});

packages/manager/src/features/Account/Maintenance/utilities.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,17 @@ export const deriveMaintenanceStartISO = (
8383

8484
/**
8585
* Build a user-friendly relative label for the Upcoming table.
86-
* - Prefers the actual/derived start time to express time until maintenance
87-
* - Falls back to the notice relative time when start cannot be determined
86+
*
87+
* Behavior:
88+
* - Prefers the actual or policy-derived start time to express time until maintenance
89+
* - Falls back to the notice relative time when the start cannot be determined
8890
* - Avoids day-only rounding by showing days + hours when >= 1 day
91+
*
92+
* Formatting rules:
93+
* - "in X days Y hours" when >= 1 day
94+
* - "in X hours" when >= 1 hour and < 1 day
95+
* - "in N minutes" when < 1 hour
96+
* - "in N seconds" when < 1 minute
8997
*/
9098
export const getUpcomingRelativeLabel = (
9199
maintenance: AccountMaintenance,
@@ -107,18 +115,39 @@ export const getUpcomingRelativeLabel = (
107115
return startDT.toRelative() ?? '—';
108116
}
109117

110-
// Avoid day-only rounding near boundaries by including hours alongside days
111-
const diff = startDT.diff(now, ['days', 'hours']).toObject();
118+
// Avoid day-only rounding near boundaries by including hours alongside days.
119+
// For times under an hour, show exact minutes remaining; under a minute, show seconds.
120+
const diff = startDT
121+
.diff(now, ['days', 'hours', 'minutes', 'seconds'])
122+
.toObject();
112123
let days = Math.floor(diff.days ?? 0);
113-
let hours = Math.round(diff.hours ?? 0);
124+
let hours = Math.floor(diff.hours ?? 0);
125+
let minutes = Math.round(diff.minutes ?? 0);
126+
const seconds = Math.round(diff.seconds ?? 0);
127+
128+
// Normalize minute/hour boundaries
129+
if (minutes === 60) {
130+
hours += 1;
131+
minutes = 0;
132+
}
114133
if (hours === 24) {
115134
days += 1;
116135
hours = 0;
117136
}
137+
118138
if (days >= 1) {
119139
const dayPart = pluralize('day', 'days', days);
120140
const hourPart = hours ? ` ${pluralize('hour', 'hours', hours)}` : '';
121141
return `in ${dayPart}${hourPart}`;
122142
}
123-
return startDT.toRelative({ unit: 'hours', round: true }) ?? '—';
143+
144+
if (hours >= 1) {
145+
return `in ${pluralize('hour', 'hours', hours)}`;
146+
}
147+
148+
// Under one hour: show minutes; under one minute: show seconds
149+
if (minutes === 0) {
150+
return `in ${pluralize('second', 'seconds', Math.max(0, seconds))}`;
151+
}
152+
return `in ${pluralize('minute', 'minutes', Math.max(0, minutes))}`;
124153
};

0 commit comments

Comments
 (0)