Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d5cf137
feat(web): add TanStack Query
imobachgs Jul 2, 2024
1054332
feat(web): use React Query to fetch locales data
imobachgs Jul 2, 2024
f975f0f
refactor(web): move QueryClientProvider to app context
imobachgs Jul 2, 2024
503e372
refactor(web): expose the client's websocket
imobachgs Jul 3, 2024
e263dbe
refactor(web): use queries to listen for l10n changes
imobachgs Jul 3, 2024
7472ab8
refactor(web): adapt L10nPage to use queries
imobachgs Jul 3, 2024
5d6232a
refactor(web): adapt L10nSection to use queries
imobachgs Jul 3, 2024
2b83836
refactor(web): use ~/queries instead of relative paths
imobachgs Jul 3, 2024
eeeb61a
refactor(web): use functions to build queries
imobachgs Jul 3, 2024
82c20df
fix(web): minimize l10n data retrieving
imobachgs Jul 3, 2024
37a7e80
refactor(web): move l10n routes to ~/routes
dgdavid Jul 3, 2024
227c42f
refactor(web): adapt InstallerOptions to use queries
imobachgs Jul 3, 2024
179b65e
fix(web): add missing file
dgdavid Jul 3, 2024
79ab607
refactor(web): drop the l10n context
imobachgs Jul 3, 2024
4c02c9c
test(web): adapt test-utils to react-query
imobachgs Jul 3, 2024
059ba54
test(web): adapt basic L10nPage test to react-query
imobachgs Jul 3, 2024
8bbac2c
refactor(web): improve l10n selectors using RR loaders
dgdavid Jul 3, 2024
a8543eb
fix(web): listen for L10n changes from App
imobachgs Jul 3, 2024
bc6ff01
fix(web): fix locales update
imobachgs Jul 3, 2024
ccaa0f0
fix(web): revalidate loader on config change
dgdavid Jul 4, 2024
c0367be
fix(web): add a role to CardField
imobachgs Jul 4, 2024
122bf20
test(web): write a new test for L10nPage
imobachgs Jul 4, 2024
0fd96ff
test: drop the queryRender helper
imobachgs Jul 4, 2024
e018d5f
doc(web): document l10n queries
imobachgs Jul 4, 2024
3cdcaf6
fix(web): make ESLint happy
imobachgs Jul 4, 2024
58a27ff
refactor(web): remove unused methods from L10nClient
imobachgs Jul 4, 2024
f633c95
test(web): fix App tests
imobachgs Jul 4, 2024
6f16921
refactor(web): drop unused L10nClient tests
imobachgs Jul 4, 2024
4766a2d
feat(web): wrap plainRender on a QueryClientProvider
imobachgs Jul 4, 2024
b893146
test(web): fix L10nSectino tests
imobachgs Jul 4, 2024
5236f86
test(web): remove uneeded mock from L10nSection test
imobachgs Jul 4, 2024
82267a9
feat(web): add a hook for invalidating cached data
dgdavid Jul 4, 2024
2ead884
fix(web): please ESLint
dgdavid Jul 4, 2024
b841272
test(web): add tests for l10n selectors
imobachgs Jul 5, 2024
c9ee944
fix(web): drop repeated invalidation
dgdavid Jul 8, 2024
dc28137
Merge branch 'master' into react-query
imobachgs Jul 8, 2024
b4e169a
doc(web): update the changes file
imobachgs Jul 8, 2024
cbbb727
doc(fix): fix changes entry
imobachgs Jul 8, 2024
8838564
fix(service): fix manager tests
imobachgs Jul 8, 2024
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
25 changes: 25 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
"@patternfly/patternfly": "^5.1.0",
"@patternfly/react-core": "^5.1.1",
"@patternfly/react-table": "^5.1.1",
"@tanstack/react-query": "^5.49.2",
"fast-sort": "^3.4.0",
"ipaddr.js": "^2.1.0",
"react": "^18.2.0",
Expand Down
8 changes: 8 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
-------------------------------------------------------------------
Mon Jul 8 10:56:14 UTC 2024 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

- Introduce TanStack Query to handle for data fetching and state
Comment thread
imobachgs marked this conversation as resolved.
Outdated
management (gh#openSUSE/agama#1439).
- Replace the l10n context with a solution based on TanStack
Query.

-------------------------------------------------------------------
Wed Jul 3 07:17:55 UTC 2024 - Imobach Gonzalez Sosa <igonzalezsosa@suse.com>

Expand Down
5 changes: 5 additions & 0 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import { useInstallerClientStatus } from "~/context/installer";
import { useProduct } from "./context/product";
import { CONFIG, INSTALL, STARTUP } from "~/client/phase";
import { BUSY } from "~/client/status";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useL10nConfigChanges } from "~/queries/l10n";

const queryClient = new QueryClient();

/**
* Main application component.
Expand All @@ -42,6 +46,7 @@ function App() {
const { connected, error, phase, status } = useInstallerClientStatus();
const { selectedProduct, products } = useProduct();
const { language } = useInstallerL10n();
useL10nConfigChanges();

const Content = () => {
if (error) return <ServerError />;
Expand Down
17 changes: 7 additions & 10 deletions web/src/App.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
*/

import React from "react";
import { act, screen } from "@testing-library/react";
import { screen } from "@testing-library/react";
import { installerRender } from "~/test-utils";

import App from "./App";
import { createClient } from "~/client";
import { STARTUP, CONFIG, INSTALL } from "~/client/phase";
import { IDLE, BUSY } from "~/client/status";
import { useL10nConfigChanges } from "./queries/l10n";

jest.mock("~/client");

Expand All @@ -44,6 +45,11 @@ jest.mock("~/context/product", () => ({
}
}));

jest.mock("~/queries/l10n", () => ({
...jest.requireActual("~/queries/l10n"),
useL10nConfigChanges: () => jest.fn()
}));

const mockClientStatus = {
connected: true,
error: false,
Expand All @@ -70,18 +76,9 @@ describe("App", () => {
createClient.mockImplementation(() => {
return {
l10n: {
locales: jest.fn().mockResolvedValue([["en_us", "English", "United States"]]),
getLocales: jest.fn().mockResolvedValue(["en_us"]),
timezones: jest.fn().mockResolvedValue([]),
getTimezone: jest.fn().mockResolvedValue("Europe/Berlin"),
keymaps: jest.fn().mockResolvedValue([]),
getKeymap: jest.fn().mockResolvedValue(undefined),
getUIKeymap: jest.fn().mockResolvedValue("en"),
getUILocale: jest.fn().mockResolvedValue("en_us"),
setUILocale: jest.fn().mockResolvedValue("en_us"),
onTimezoneChange: jest.fn(),
onLocalesChange: jest.fn(),
onKeymapChange: jest.fn()
}
};
});
Expand Down
4 changes: 3 additions & 1 deletion web/src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const createClient = url => {

const isConnected = () => client.ws?.isConnected() || false;
const isRecoverable = () => !!client.ws?.isRecoverable();
const ws = () => client.ws;

return {
l10n,
Expand All @@ -145,7 +146,8 @@ const createClient = url => {
isConnected,
isRecoverable,
onConnect: handler => client.ws.onOpen(handler),
onDisconnect: handler => client.ws.onClose(handler)
onDisconnect: handler => client.ws.onClose(handler),
ws: () => client.ws
};
};

Expand Down
241 changes: 0 additions & 241 deletions web/src/client/l10n.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,6 @@
*/

// @ts-check
import { timezoneUTCOffset } from "~/utils";

/**
* @typedef {object} Timezone
* @property {string} id - Timezone id (e.g., "Atlantic/Canary").
* @property {Array<string>} parts - Name of the timezone parts (e.g., ["Atlantic", "Canary"]).
* @property {string} country - Name of the country associated to the zone or empty string (e.g., "Spain").
* @property {number} utcOffset - UTC offset.
*/

/**
* @typedef {object} Locale
* @property {string} id - Language id (e.g., "en_US.UTF-8").
* @property {string} name - Language name (e.g., "English").
* @property {string} territory - Territory name (e.g., "United States").
*/

/**
* @typedef {object} Keymap
* @property {string} id - Keyboard id (e.g., "us").
* @property {string} name - Keyboard name (e.g., "English (US)").
*/

/**
* Manages localization.
Expand Down Expand Up @@ -107,225 +85,6 @@ class L10nClient {
async setUIKeymap(id) {
return this.client.patch("/l10n/config", { uiKeymap: id });
}

/**
* All possible timezones for the target system.
*
* @return {Promise<Array<Timezone>>}
*/
async timezones() {
const response = await this.client.get("/l10n/timezones");
if (!response.ok) {
console.error("Failed to get localization config: ", response);
return [];
}
const timezones = await response.json();
return timezones.map(this.buildTimezone);
}

/**
* Timezone selected for the target system.
*
* @return {Promise<String>} Id of the timezone.
*/
async getTimezone() {
const config = await this.getConfig();
return config.timezone;
}

/**
* Sets the timezone for the target system.
*
* @param {string} id - Id of the timezone.
* @return {Promise<void>}
*/
async setTimezone(id) {
this.setConfig({ timezone: id });
}

/**
* Available locales to install in the target system.
*
* TODO: find a better name because it is rather confusing (e.g., 'locales' and 'getLocales').
*
* @return {Promise<Array<Locale>>}
*/
async locales() {
const response = await this.client.get("/l10n/locales");
if (!response.ok) {
console.error("Failed to get localization config: ", response);
return [];
}
const locales = await response.json();
return locales.map(this.buildLocale);
}

/**
* Locales selected to install in the target system.
*
* @return {Promise<Array<String>>} Ids of the locales.
*/
async getLocales() {
const config = await this.getConfig();
return config.locales;
}

/**
* Sets the locales to install in the target system.
*
* @param {Array<string>} ids - Ids of the locales.
* @return {Promise<void>}
*/
async setLocales(ids) {
this.setConfig({ locales: ids });
}

/**
* Available keymaps to install in the target system.
*
* Note that name is localized to the current selected UI language:
* { id: "es", name: "Spanish (ES)" }
*
* @return {Promise<Array<Keymap>>}
*/
async keymaps() {
const response = await this.client.get("/l10n/keymaps");
if (!response.ok) {
console.error("Failed to get localization config: ", response);
return [];
}
const keymaps = await response.json();
return keymaps.map(this.buildKeymap);
}

/**
* Keymap selected to install in the target system.
*
* @return {Promise<String>} Id of the keymap.
*/
async getKeymap() {
const config = await this.getConfig();
return config.keymap;
}

/**
* Sets the keymap to install in the target system.
*
* @param {string} id - Id of the keymap.
* @return {Promise<void>}
*/
async setKeymap(id) {
this.setConfig({ keymap: id });
}

/**
* Register a callback to run when the timezone configuration changes.
*
* @param {(timezone: string) => void} handler - Function to call when Timezone changes.
* @return {import ("./http").RemoveFn} Function to disable the callback.
*/
onTimezoneChange(handler) {
return this.client.onEvent("L10nConfigChanged", ({ timezone }) => {
if (timezone) {
handler(timezone);
}
});
}

/**
* Register a callback to run when the locales configuration changes.
*
* @param {(locales: string[]) => void} handler - Function to call when Locales changes.
* @return {import ("./http").RemoveFn} Function to disable the callback.
*/
onLocalesChange(handler) {
return this.client.onEvent("L10nConfigChanged", ({ locales }) => {
if (locales) {
handler(locales);
}
});
}

/**
* Register a callback to run when the keymap configuration changes.
*
* @param {(keymap: string) => void} handler - Function to call when Keymap changes.
* @return {import ("./http").RemoveFn} Function to disable the callback.
*/
onKeymapChange(handler) {
return this.client.onEvent("L10nConfigChanged", ({ keymap }) => {
if (keymap) {
handler(keymap);
}
});
}

/**
* @private
* Convenience method to get l10n the configuration.
*
* @return {Promise<object>} Localization configuration.
*/
async getConfig() {
const response = await this.client.get("/l10n/config");
if (!response.ok) {
console.error("Failed to get localization config: ", response);
return {};
}
return await response.json();
}

/**
* @private
*
* Convenience method to set l10n the configuration.
*
* @param {object} data - Configuration to update. It can just part of the configuration.
* @return {Promise<Response>}
*/
async setConfig(data) {
return this.client.patch("/l10n/config", data);
}

/**
* @private
*
* @param {object} timezone - Timezone data.
* @param {string} timezone.code - Timezone identifier.
* @param {Array<string>} timezone.parts - Localized parts of the timezone identifier.
* @param {string} timezone.country - Timezone country.
* @return {Timezone}
*/
buildTimezone({ code, parts, country }) {
const utcOffset = timezoneUTCOffset(code);

return ({ id: code, parts, country, utcOffset });
}

/**
* @private
*
* @param {object} locale - Locale data.
* @param {string} locale.id - Identifier.
* @param {string} locale.language - Name.
* @param {string} locale.territory - Territory.
* @return {Locale}
*/
buildLocale({ id, language, territory }) {
return ({ id, name: language, territory });
}

/**
* @private
*
* @param {object} keymap - Keymap data
* @param {string} keymap.id - Id (e.g., "us").
* @param {string} keymap.description - Keymap description (e.g., "English (US)").
* @return {Keymap}
*/
buildKeymap({ id, description }) {
return ({ id, name: description });
}
}

export { L10nClient };
Loading