Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/gmp/commands/__tests__/timezones.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {describe, test, expect} from '@gsa/testing';
import {createResponse, createHttp} from 'gmp/commands/testing';
import TimezonesCommand from 'gmp/commands/timezones';

describe('TimezonesCommand tests', () => {
test('should return timezones list', async () => {
const response = createResponse({
get_timezones: {
get_timezones_response: {
timezone: [
{name: 'UTC'},
{name: 'Europe/Berlin'},
{name: 'America/New_York'},
{name: 'Asia/Tokyo'},
],
},
},
});

const fakeHttp = createHttp(response);

const cmd = new TimezonesCommand(fakeHttp);
const resp = await cmd.get();

expect(fakeHttp.request).toHaveBeenCalledWith('get', {
args: {
cmd: 'get_timezones',
},
});

const {data} = resp;
expect(data).toHaveLength(4);
expect(data).toEqual([
'UTC',
'Europe/Berlin',
'America/New_York',
'Asia/Tokyo',
]);
});

test('should handle empty timezones list', async () => {
const response = createResponse({
get_timezones: {
get_timezones_response: {
timezone: [],
},
},
});

const fakeHttp = createHttp(response);

const cmd = new TimezonesCommand(fakeHttp);
const resp = await cmd.get();

expect(fakeHttp.request).toHaveBeenCalledWith('get', {
args: {
cmd: 'get_timezones',
},
});

const {data} = resp;
expect(data).toHaveLength(0);
expect(data).toEqual([]);
});
});
25 changes: 25 additions & 0 deletions src/gmp/commands/timezones.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* SPDX-FileCopyrightText: 2026 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import HttpCommand from 'gmp/commands/http';
import type Http from 'gmp/http/http';
import {map} from 'gmp/utils/array';

class TimezonesCommand extends HttpCommand {
constructor(http: Http) {
super(http, {cmd: 'get_timezones'});
}

async get() {
const response = await this.httpGetWithTransform();
const {data} = response;
const {timezone: timezones} =
// @ts-expect-error
data.get_timezones.get_timezones_response;
return response.set(map(timezones, tz => tz.name));
}
}

export default TimezonesCommand;
3 changes: 3 additions & 0 deletions src/gmp/gmp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import TargetCommand from 'gmp/commands/target';
import TargetsCommand from 'gmp/commands/targets';
import TaskCommand from 'gmp/commands/task';
import TasksCommand from 'gmp/commands/tasks';
import TimezonesCommand from 'gmp/commands/timezones';
import TrashCanCommand from 'gmp/commands/trashcan';
import UserCommand from 'gmp/commands/user';
import UsersCommand from 'gmp/commands/users';
Expand Down Expand Up @@ -139,6 +140,7 @@ class Gmp {
readonly targets: TargetsCommand;
readonly task: TaskCommand;
readonly tasks: TasksCommand;
readonly timezones: TimezonesCommand;
readonly trashcan: TrashCanCommand;
readonly user: UserCommand;
readonly users: UsersCommand;
Expand Down Expand Up @@ -205,6 +207,7 @@ class Gmp {
this.targets = new TargetsCommand(this.http);
this.task = new TaskCommand(this.http);
this.tasks = new TasksCommand(this.http);
this.timezones = new TimezonesCommand(this.http);
this.trashcan = new TrashCanCommand(this.http);
this.user = new UserCommand(this.http);
this.users = new UsersCommand(this.http);
Expand Down
10 changes: 8 additions & 2 deletions src/web/components/form/TimeZoneSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {useMemo} from 'react';
import timezones, {DEFAULT_TIMEZONE} from 'gmp/time-zones';
import Select, {type SelectProps} from 'web/components/form/Select';
import {useGetTimezones} from 'web/hooks/use-query/timezones';

interface TimeZoneSelectProps extends Omit<
SelectProps<string>,
Expand All @@ -18,13 +19,18 @@ const TimeZoneSelectComponent = ({
value = DEFAULT_TIMEZONE,
...props
}: TimeZoneSelectProps) => {
const {data: fetchedTimezones} = useGetTimezones();

// Use fetched timezones if available, otherwise fall back to hardcoded list
const timezoneList = fetchedTimezones ?? timezones;

const timezoneItems = useMemo(
() =>
timezones.map(name => ({
timezoneList.map(name => ({
label: name,
value: name,
})),
[],
[timezoneList],
);

return <Select {...props} items={timezoneItems} value={value} />;
Expand Down
38 changes: 37 additions & 1 deletion src/web/components/form/__tests__/TimeZoneSelect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,33 @@
*/

import {describe, test, expect, testing} from '@gsa/testing';
import {openSelectElement, screen, fireEvent, render} from 'web/testing';
import {openSelectElement, screen, fireEvent, rendererWith} from 'web/testing';
import Response from 'gmp/http/response';
import timezones from 'gmp/time-zones';
import TimezoneSelect from 'web/components/form/TimeZoneSelect';

describe('TimezoneSelect tests', () => {
test('should render', () => {
const gmp = {
settings: {token: 'token'},
timezones: {
get: testing.fn().mockResolvedValue(new Response(timezones)),
},
};
const {render} = rendererWith({gmp});
const {element} = render(<TimezoneSelect />);

expect(element).toBeInTheDocument();
});

test('should render all timezones in selection', async () => {
const gmp = {
settings: {token: 'token'},
timezones: {
get: testing.fn().mockResolvedValue(new Response(timezones)),
},
};
const {render} = rendererWith({gmp});
render(<TimezoneSelect />);

await openSelectElement();
Expand All @@ -25,6 +40,13 @@ describe('TimezoneSelect tests', () => {

test('should call onChange handler', async () => {
const handler = testing.fn();
const gmp = {
settings: {token: 'token'},
timezones: {
get: testing.fn().mockResolvedValue(new Response(timezones)),
},
};
const {render} = rendererWith({gmp});
render(<TimezoneSelect onChange={handler} />);

await openSelectElement();
Expand All @@ -37,6 +59,13 @@ describe('TimezoneSelect tests', () => {

test('should call onChange handler with name', async () => {
const handler = testing.fn();
const gmp = {
settings: {token: 'token'},
timezones: {
get: testing.fn().mockResolvedValue(new Response(timezones)),
},
};
const {render} = rendererWith({gmp});
render(<TimezoneSelect name="foo" onChange={handler} />);

await openSelectElement();
Expand All @@ -49,6 +78,13 @@ describe('TimezoneSelect tests', () => {

test('should render selected value', () => {
const timezone = timezones[1];
const gmp = {
settings: {token: 'token'},
timezones: {
get: testing.fn().mockResolvedValue(new Response(timezones)),
},
};
const {render} = rendererWith({gmp});
render(<TimezoneSelect value={timezone} />);

const input = screen.getSelectElement();
Expand Down
27 changes: 27 additions & 0 deletions src/web/hooks/use-query/timezones.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {useQuery} from '@tanstack/react-query';
import useGmp from 'web/hooks/useGmp';

interface UseGetTimezonesParams {
enabled?: boolean;
}

export const useGetTimezones = ({
enabled = true,
}: UseGetTimezonesParams = {}) => {
const gmp = useGmp();
const {token} = gmp.settings;

return useQuery({
enabled: enabled && !!token,
queryKey: ['get_timezones', token],
queryFn: async () => {
const response = await gmp.timezones.get();
return response.data;
},
});
};
Loading