diff --git a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/IntentSettings.java b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/IntentSettings.java index 37a8ff21b8f..ddbdf8e5db7 100644 --- a/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/IntentSettings.java +++ b/components/engine/engine-intent/src/main/java/org/eclipse/dirigible/components/intent/generator/IntentSettings.java @@ -21,6 +21,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; /** * The {@code .settings} document: the per-project recipe for turning the generated model @@ -121,9 +123,41 @@ public static IntentSettings parse(String json) { if (settings == null) { settings = new IntentSettings(); } + if (!hasUserTasksSection(json)) { + // A .settings scaffolded before the userTasks setting existed carries no such section - + // it must keep the documented ADMINISTRATOR default, exactly as scaffolding would have + // written it. Only an EXPLICIT userTasks block opts out (an empty candidateGroupsExtra + // is a deliberate "no extra groups"). Without this, every pre-existing settings file + // silently emitted single-group tasks that no administrator inbox could see. + if (settings.userTasks == null) { + settings.userTasks = new UserTasks(); + } + if (settings.userTasks.candidateGroupsExtra == null) { + settings.userTasks.candidateGroupsExtra = new ArrayList<>(); + } + settings.userTasks.candidateGroupsExtra.add("ADMINISTRATOR"); + } return settings; } + /** + * Whether the raw settings document declares a {@code userTasks} member at all (a {@code null} + * member counts as absent). The deserialized POJO cannot answer this - a missing section and an + * explicitly empty one both surface as an empty list. + */ + private static boolean hasUserTasksSection(String json) { + if (json == null || json.isBlank()) { + return false; + } + JsonElement root = JsonParser.parseString(json); + if (!root.isJsonObject()) { + return false; + } + JsonElement userTasks = root.getAsJsonObject() + .get("userTasks"); + return userTasks != null && !userTasks.isJsonNull(); + } + /** * Build the initial settings for a model: the default template recipes plus a {@code generate:true} * entry per discoverable trigger / resolver / form (so the developer sees the full editable list) diff --git a/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/IntentSettingsTest.java b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/IntentSettingsTest.java new file mode 100644 index 00000000000..bbe6149a582 --- /dev/null +++ b/components/engine/engine-intent/src/test/java/org/eclipse/dirigible/components/intent/generator/IntentSettingsTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2026 Eclipse Dirigible contributors + * + * All rights reserved. This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.dirigible.components.intent.generator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * The {@code userTasks.candidateGroupsExtra} default contract: a settings document that predates + * the section (or omits it) keeps the documented ADMINISTRATOR default, while an explicit block - + * including an explicitly empty list, a deliberate opt-out - means exactly what it says. Without + * the on-load default, every pre-existing {@code .settings} silently emitted single-group tasks + * invisible to administrators (GH-6338). + */ +class IntentSettingsTest { + + @Test + void aSettingsFileWithoutTheUserTasksSectionKeepsTheAdministratorDefault() { + IntentSettings settings = IntentSettings.parse(""" + { + "generation": {}, + "overrides": {} + } + """); + assertEquals(List.of("ADMINISTRATOR"), settings.candidateGroupsExtra()); + } + + @Test + void anEmptyDocumentKeepsTheAdministratorDefault() { + assertEquals(List.of("ADMINISTRATOR"), IntentSettings.parse("{}") + .candidateGroupsExtra()); + } + + @Test + void aNullUserTasksSectionCountsAsAbsent() { + assertEquals(List.of("ADMINISTRATOR"), IntentSettings.parse("{\"userTasks\": null}") + .candidateGroupsExtra()); + } + + @Test + void anExplicitlyEmptyListIsADeliberateOptOut() { + IntentSettings settings = IntentSettings.parse(""" + { + "userTasks": { "candidateGroupsExtra": [] } + } + """); + assertTrue(settings.candidateGroupsExtra() + .isEmpty()); + } + + @Test + void anExplicitListIsKeptVerbatim() { + IntentSettings settings = IntentSettings.parse(""" + { + "userTasks": { "candidateGroupsExtra": ["Supervisor"] } + } + """); + assertEquals(List.of("Supervisor"), settings.candidateGroupsExtra()); + } +}