Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package agama-web-ui for openSUSE:Factory checked in at 2026-01-30 18:19:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/agama-web-ui (Old) and /work/SRC/openSUSE:Factory/.agama-web-ui.new.1995 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "agama-web-ui" Fri Jan 30 18:19:40 2026 rev:34 rq:1329818 version:0 Changes: -------- --- /work/SRC/openSUSE:Factory/agama-web-ui/agama-web-ui.changes 2026-01-28 15:06:18.655914354 +0100 +++ /work/SRC/openSUSE:Factory/.agama-web-ui.new.1995/agama-web-ui.changes 2026-01-30 18:20:03.451930254 +0100 @@ -1,0 +2,20 @@ +Thu Jan 29 10:26:49 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Do not crash when selecting a pattern (gh#agama-project/agama#3097, + bsc#1257454). + +------------------------------------------------------------------- +Wed Jan 28 12:43:13 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Force the user to select a mode when it is needed (related to + jsc#PED-14307). +- Do not loose the mode when registering a product (related to + jsc#PED-14307). +- Reset the configuration when selecting a new product. + +------------------------------------------------------------------- +Wed Jan 28 11:00:28 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Allow selecting the mode in the product selection screen (jsc#PED-14307). + +------------------------------------------------------------------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ agama.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/package/agama-web-ui.changes new/agama/package/agama-web-ui.changes --- old/agama/package/agama-web-ui.changes 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/package/agama-web-ui.changes 2026-01-29 11:55:42.000000000 +0100 @@ -1,4 +1,24 @@ ------------------------------------------------------------------- +Thu Jan 29 10:26:49 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Do not crash when selecting a pattern (gh#agama-project/agama#3097, + bsc#1257454). + +------------------------------------------------------------------- +Wed Jan 28 12:43:13 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Force the user to select a mode when it is needed (related to + jsc#PED-14307). +- Do not loose the mode when registering a product (related to + jsc#PED-14307). +- Reset the configuration when selecting a new product. + +------------------------------------------------------------------- +Wed Jan 28 11:00:28 UTC 2026 - Imobach Gonzalez Sosa <[email protected]> + +- Allow selecting the mode in the product selection screen (jsc#PED-14307). + +------------------------------------------------------------------- Fri Jan 23 15:34:08 UTC 2026 - Ladislav Slezák <[email protected]> - Fixed removing automatically selected recommended patterns diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/core/ChangeProductOption.test.tsx new/agama/src/components/core/ChangeProductOption.test.tsx --- old/agama/src/components/core/ChangeProductOption.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/core/ChangeProductOption.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -24,9 +24,9 @@ import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { useSystem } from "~/hooks/model/system"; -import { Product } from "~/types/software"; import { PRODUCT as PATHS } from "~/routes/paths"; import ChangeProductOption from "./ChangeProductOption"; +import { Product } from "~/model/system"; const tumbleweed: Product = { id: "Tumbleweed", @@ -34,6 +34,7 @@ icon: "tumbleweed.svg", description: "Tumbleweed description...", registration: false, + modes: [], }; const microos: Product = { @@ -42,6 +43,7 @@ icon: "MicroOS.svg", description: "MicroOS description", registration: false, + modes: [], }; // let registrationInfoMock: RegistrationInfo; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/core/ChangeProductOption.tsx new/agama/src/components/core/ChangeProductOption.tsx --- old/agama/src/components/core/ChangeProductOption.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/core/ChangeProductOption.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -28,6 +28,7 @@ import { _ } from "~/i18n"; import { useSystem } from "~/hooks/model/system"; import { useStatus } from "~/hooks/model/status"; +import { isEmpty } from "radashi"; /** * DropdownItem Option for navigating to the selection product. @@ -37,15 +38,22 @@ const { stage } = useStatus(); const currentLocation = useLocation(); const to = useHref(PATHS.changeProduct); + const hasModes = products.find((p) => !isEmpty(p.modes)); - if (products.length <= 1) return null; + if (products.length <= 1 && !hasModes) return null; if (software?.registration) return null; if (SIDE_PATHS.includes(currentLocation.pathname)) return null; if (stage !== "configuring") return null; + const getLabel = () => { + if (products.length === 1 && hasModes) return _("Change mode"); + if (hasModes) return _("Change product or mode"); + return _("Change product"); + }; + return ( <DropdownItem to={to} {...props}> - {children || _("Change product")} + {children || getLabel()} </DropdownItem> ); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/layout/Header.test.tsx new/agama/src/components/layout/Header.test.tsx --- old/agama/src/components/layout/Header.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/layout/Header.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -23,16 +23,17 @@ import React from "react"; import { screen, within } from "@testing-library/react"; import { plainRender, installerRender } from "~/test-utils"; -import { Product } from "~/types/software"; import { System } from "~/model/system/network"; import Header from "./Header"; import { useSystem } from "~/hooks/model/system"; +import { Product } from "~/model/system"; const tumbleweed: Product = { id: "Tumbleweed", name: "openSUSE Tumbleweed", description: "Tumbleweed description...", registration: false, + modes: [], }; const microos: Product = { @@ -40,6 +41,7 @@ name: "openSUSE MicroOS", description: "MicroOS description", registration: false, + modes: [], }; const network: System = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/product/ProductRegistrationPage.test.tsx new/agama/src/components/product/ProductRegistrationPage.test.tsx --- old/agama/src/components/product/ProductRegistrationPage.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/product/ProductRegistrationPage.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -35,12 +35,14 @@ id: "Tumbleweed", name: "openSUSE Tumbleweed", registration: false, + modes: [], }; const sle: Product = { id: "sle", name: "SLE", registration: true, + modes: [], }; let mockSelectedProduct: Product | undefined; @@ -78,7 +80,7 @@ describe("ProductRegistrationPage", () => { beforeEach(() => { - mockConfig = { product: { id: "sle", registrationCode: "" } }; + mockConfig = { product: { id: "sle", mode: "standard", registrationCode: "" } }; mockIssues = []; mockProductConfig(mockConfig.product); // @ts-ignore @@ -142,6 +144,7 @@ ...mockConfig, product: { id: "sle", + mode: "standard", registrationCode: "INTERNAL-USE-ONLY-1234-5678", registrationEmail: undefined, registrationUrl: undefined, @@ -170,6 +173,7 @@ ...mockConfig, product: { id: "sle", + mode: "standard", registrationCode: "INTERNAL-USE-ONLY-1234-5678", registrationEmail: "[email protected]", registrationUrl: undefined, @@ -209,6 +213,7 @@ ...mockConfig, product: { id: "sle", + mode: "standard", registrationUrl: "https://custom-server.test", registrationCode: undefined, registrationEmail: undefined, @@ -270,6 +275,7 @@ ...mockConfig, product: { id: "sle", + mode: "standard", registrationUrl: "https://custom-server.test", registrationCode: "INTERNAL-USE-ONLY-1234-5678", registrationEmail: undefined, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/product/ProductRegistrationPage.tsx new/agama/src/components/product/ProductRegistrationPage.tsx --- old/agama/src/components/product/ProductRegistrationPage.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/product/ProductRegistrationPage.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -328,6 +328,7 @@ ...config, product: { id: product.id, + mode: product.mode, registrationCode: isKeyRequired ? key : undefined, registrationEmail: provideEmail ? email : undefined, registrationUrl: isUrlRequired ? url : undefined, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/product/ProductSelectionPage.test.tsx new/agama/src/components/product/ProductSelectionPage.test.tsx --- old/agama/src/components/product/ProductSelectionPage.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/product/ProductSelectionPage.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -25,9 +25,9 @@ import { installerRender, mockNavigateFn, mockProduct } from "~/test-utils"; import { useSystem } from "~/hooks/model/system"; import { useSystem as useSystemSoftware } from "~/hooks/model/system/software"; -import { Product } from "~/types/software"; import { ROOT } from "~/routes/paths"; import ProductSelectionPage from "./ProductSelectionPage"; +import { Product } from "~/model/system"; const tumbleweed: Product = { id: "Tumbleweed", @@ -35,6 +35,7 @@ icon: "tumbleweed.svg", description: "Tumbleweed description...", registration: false, + modes: [], }; const microOs: Product = { @@ -44,9 +45,10 @@ description: "MicroOS description", registration: false, license: "fake.license", + modes: [], }; -const mockPatchConfigFn = jest.fn(); +const mockPutConfigFn = jest.fn(); const mockUseSystemFn: jest.Mock<ReturnType<typeof useSystem>> = jest.fn(); const mockUseSystemSoftwareFn: jest.Mock<ReturnType<typeof useSystemSoftware>> = jest.fn(); @@ -59,7 +61,7 @@ jest.mock("~/api", () => ({ ...jest.requireActual("~/api"), - patchConfig: (payload) => mockPatchConfigFn(payload), + putConfig: (payload) => mockPutConfigFn(payload), })); jest.mock("~/hooks/model/system", () => ({ @@ -181,7 +183,7 @@ const selectButton = screen.getByRole("button", { name: "Select" }); await user.click(productOption); await user.click(selectButton); - expect(mockPatchConfigFn).toHaveBeenCalledWith({ product: { id: tumbleweed.id } }); + expect(mockPutConfigFn).toHaveBeenCalledWith({ product: { id: tumbleweed.id } }); }); it("does not trigger the product selection if user selects a product but clicks o cancel button", async () => { @@ -192,7 +194,7 @@ expect(cancel).toHaveAttribute("href", ROOT.overview); await user.click(productOption); await user.click(cancel); - expect(mockPatchConfigFn).not.toHaveBeenCalled(); + expect(mockPutConfigFn).not.toHaveBeenCalled(); }); it.todo("make navigation test work"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/product/ProductSelectionPage.tsx new/agama/src/components/product/ProductSelectionPage.tsx --- old/agama/src/components/product/ProductSelectionPage.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/product/ProductSelectionPage.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -56,8 +56,8 @@ import LicenseDialog from "~/components/product/LicenseDialog"; import Text from "~/components/core/Text"; import agama from "~/agama"; -import { patchConfig } from "~/api"; -import { useProductInfo } from "~/hooks/model/config/product"; +import { putConfig } from "~/api"; +import { useProduct, useProductInfo } from "~/hooks/model/config/product"; import { useSystem } from "~/hooks/model/system"; import { useSystem as useSystemSoftware } from "~/hooks/model/system/software"; import { ROOT } from "~/routes/paths"; @@ -76,6 +76,8 @@ isChecked: boolean; /** Callback fired when the product is selected */ onChange: () => void; + /** Callback fired when the mode is changed */ + onModeChange: (mode: string) => void; }; /** @@ -85,6 +87,7 @@ product, isChecked, onChange, + onModeChange, }: ProductFormProductOptionProps) => { const detailsId = `${product.id}-details`; const currentLocale = agama.language.replace("-", "_"); @@ -111,13 +114,20 @@ } body={ <Stack hasGutter id={detailsId}> - {product.license && ( + {(product.license || product.modes) && ( <Split hasGutter> {product.license && ( <Label variant="outline" isCompact> <Text component="small">{_("License acceptance required")}</Text> </Label> )} + {!isEmpty(product.modes) && ( + <Label variant="outline" isCompact> + <Text component="small"> + {sprintf(_("%d modes available"), product.modes.length)} + </Text> + </Label> + )} </Split> )} @@ -129,6 +139,22 @@ > <SubtleContent>{translatedDescription}</SubtleContent> </ExpandableSection> + {isChecked && product.modes && ( + <Split hasGutter> + {product.modes.map((mode) => ( + <FlexItem key={mode.id}> + <Radio + key={mode.id} + id={mode.id} + name="mode" + onChange={() => onModeChange(mode.id)} + label={<Text isBold>{mode.name}</Text>} + description={mode.description} + /> + </FlexItem> + ))} + </Split> + )} </Stack> } /> @@ -230,6 +256,7 @@ currentProduct, selectedProduct, }: ProductFormSubmitLabelProps) => { + // FIXME: add logic to include information about the mode const action = currentProduct ? _("Change to %s") : _("Select %s"); const fallback = currentProduct ? _("Change") : _("Select"); @@ -293,7 +320,7 @@ /** The product currently configured in the system */ currentProduct?: Product; /** Callback fired when the form is submitted with a selected product */ - onSubmit: (product: Product) => void; + onSubmit: (product: Product, mode: string) => void; /** Whether the form was already submitted */ isSubmitted: boolean; }; @@ -327,23 +354,30 @@ * * Manages product selection state, license acceptance, and form validation. * Excludes the current product from the list of options. + * + * TODO: use a reducer instead of bunch of isolated state pieces */ const ProductForm = ({ products, currentProduct, isSubmitted, onSubmit }: ProductFormProps) => { const [selectedProduct, setSelectedProduct] = useState<Product>(); + const [selectedMode, setSelectedMode] = useState<string>(); const [eulaAccepted, setEulaAccepted] = useState(false); const mountEulaCheckbox = selectedProduct && !isEmpty(selectedProduct.license); const isSelectionDisabled = - !selectedProduct || isSubmitted || (mountEulaCheckbox && !eulaAccepted); + !selectedProduct || + isSubmitted || + (mountEulaCheckbox && !eulaAccepted) || + (!isEmpty(selectedProduct.modes) && !selectedMode); const onProductSelectionChange = (product) => { setEulaAccepted(false); + setSelectedMode(undefined); setSelectedProduct(product); }; const onFormSubmission = (e: React.FormEvent) => { e.preventDefault(); - onSubmit(selectedProduct); + onSubmit(selectedProduct, selectedMode); }; return ( @@ -359,7 +393,7 @@ > <List isPlain> {products.map((product, index) => { - if (product.id === currentProduct?.id) return undefined; + if (product.id === currentProduct?.id && !product.modes) return undefined; return ( <ProductFormProductOption @@ -367,6 +401,7 @@ product={product} isChecked={selectedProduct?.id === product?.id} onChange={() => onProductSelectionChange(product)} + onModeChange={setSelectedMode} /> ); })} @@ -422,6 +457,8 @@ type CurrentProductInfoProps = { /** The currently configured product to display */ product?: Product; + /** The selected mode */ + modeId?: string; }; /** @@ -429,9 +466,14 @@ * * Shows product name, description, and a link to view the license if applicable. */ -const CurrentProductInfo = ({ product }: CurrentProductInfoProps) => { +const CurrentProductInfo = ({ product, modeId }: CurrentProductInfoProps) => { if (!product) return; + let mode; + if (modeId) { + mode = product.modes.find((m) => m.id === modeId); + } + return ( <Card variant="secondary" component="section" className="sticky-top"> <CardTitle component="h2">{_("Current selection")}</CardTitle> @@ -443,6 +485,15 @@ <Divider /> <SubtleContent>{product.description}</SubtleContent> + {mode && ( + <> + <Title headingLevel="h3">{mode.name}</Title> + + <Divider /> + <SubtleContent>{mode.description}</SubtleContent> + </> + )} + {product.license && ( <LicenseButton product={product} variant="secondary" isInline> {_("View license")} @@ -465,6 +516,7 @@ */ const ProductSelectionContent = () => { const navigate = useNavigate(); + const product = useProduct(); const { products } = useSystem(); const currentProduct = useProductInfo(); const [submittedSelection, setSubmmitedSelection] = useState<Product>(); @@ -479,10 +531,11 @@ } }, [navigate, isSubmitted, currentProduct, submittedSelection]); - const onSubmit = async (selectedProduct: Product) => { + const onSubmit = async (selectedProduct: Product, selectedMode: string) => { setIsSubmmited(true); setSubmmitedSelection(selectedProduct); - patchConfig({ product: { id: selectedProduct.id } }); + // FIXME: use Mode as expected + putConfig({ product: { id: selectedProduct.id, mode: selectedMode } }); }; const introText = n_( @@ -518,7 +571,7 @@ /> </GridItem> <GridItem sm={12} md={4} order={{ default: "0", md: "1" }}> - {!isWaiting && <CurrentProductInfo product={currentProduct} />} + {!isWaiting && <CurrentProductInfo product={currentProduct} modeId={product?.mode} />} </GridItem> </Grid> </Page.Content> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/questions/LuksActivationQuestion.test.tsx new/agama/src/components/questions/LuksActivationQuestion.test.tsx --- old/agama/src/components/questions/LuksActivationQuestion.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/questions/LuksActivationQuestion.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -27,9 +27,9 @@ import { useProductInfo } from "~/hooks/model/config/product"; import { installerRender } from "~/test-utils"; import { AnswerCallback, Question, FieldType } from "~/model/question"; -import { Product } from "~/types/software"; import type { Locale, Keymap } from "~/model/system/l10n"; import LuksActivationQuestion from "~/components/questions/LuksActivationQuestion"; +import { Product } from "~/model/system"; let question: Question; const questionMock: Question = { @@ -50,6 +50,7 @@ icon: "tumbleweed.svg", description: "Tumbleweed description...", registration: false, + modes: [], }; const locales: Locale[] = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/questions/QuestionWithPassword.test.tsx new/agama/src/components/questions/QuestionWithPassword.test.tsx --- old/agama/src/components/questions/QuestionWithPassword.test.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/questions/QuestionWithPassword.test.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -24,12 +24,12 @@ import { screen } from "@testing-library/react"; import { installerRender } from "~/test-utils"; import { Question, FieldType } from "~/model/question"; -import { Product } from "~/types/software"; import { useSystem } from "~/hooks/model/system"; import { useProductInfo } from "~/hooks/model/config/product"; import { useStatus } from "~/hooks/model/status"; import { Locale, Keymap } from "~/model/system/l10n"; import QuestionWithPassword from "~/components/questions/QuestionWithPassword"; +import { Product } from "~/model/system"; const answerFn = jest.fn(); const question: Question = { @@ -50,6 +50,7 @@ icon: "tumbleweed.svg", description: "Tumbleweed description...", registration: false, + modes: [], }; const locales: Locale[] = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/components/software/SoftwarePatternsSelection.tsx new/agama/src/components/software/SoftwarePatternsSelection.tsx --- old/agama/src/components/software/SoftwarePatternsSelection.tsx 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/components/software/SoftwarePatternsSelection.tsx 2026-01-29 11:55:42.000000000 +0100 @@ -107,7 +107,8 @@ */ function SoftwarePatternsSelection(): React.ReactNode { const { patterns } = useSystem(); - const { patterns: selection } = useProposal(); + const proposal = useProposal(); + const selection = proposal?.patterns || []; const [searchValue, setSearchValue] = useState(""); const onToggle = (name: string, selected: boolean) => { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/model/config/product.ts new/agama/src/model/config/product.ts --- old/agama/src/model/config/product.ts 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/model/config/product.ts 2026-01-29 11:55:42.000000000 +0100 @@ -22,6 +22,7 @@ type Config = { id?: string; + mode?: string; registrationCode?: string; registrationEmail?: string; registrationUrl?: string; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/agama/src/model/system.ts new/agama/src/model/system.ts --- old/agama/src/model/system.ts 2026-01-26 15:26:53.000000000 +0100 +++ new/agama/src/model/system.ts 2026-01-29 11:55:42.000000000 +0100 @@ -55,6 +55,16 @@ /** The key is the locale (e.g., "en", "pt_BR") */ description: Record<string, string>; }; + modes: Mode[]; }; -export type { System, Product, L10n, Hardware, Hostname, Network, Software, Storage }; +type Mode = { + /** Mode ID (e.g., "traditional") */ + id: string; + /** Mode name (e.g., "Traditional") */ + name: string; + /** Mode description (e.g., "Traditional system") */ + description: string; +}; + +export type { System, Product, L10n, Hardware, Hostname, Mode, Network, Software, Storage }; ++++++ agama.obsinfo ++++++ --- /var/tmp/diff_new_pack.9NqKe2/_old 2026-01-30 18:20:27.620938702 +0100 +++ /var/tmp/diff_new_pack.9NqKe2/_new 2026-01-30 18:20:27.636939370 +0100 @@ -1,5 +1,5 @@ name: agama -version: 19.pre+1189.efc3a4978 -mtime: 1769437613 -commit: efc3a497809aa4a54ee0169dd81aa92f5eab5e64 +version: 19.pre+1272.5cc4683a6 +mtime: 1769684142 +commit: 5cc4683a6ab4f762313e05e8e14cd9deea4ab46a
