Skip to content

Commit e1cc0de

Browse files
thboileauGitHub Copilot
andauthored
feat(QTDI-2979): Add labelDisplayMode attribute to @suggestable (#1238)
* feat(QTDI-2979): Add labelDisplayMode attribute to @suggestable Add a LabelDisplayMode enum (LABEL, LABEL_ID) and a labelDisplayMode() annotation attribute to @suggestable. The attribute defaults to LABEL, preserving full backward compatibility. When two or more suggestions share the same label, setting labelDisplayMode = LABEL_ID allows the front-end to show both the label and the opaque identifier so the user can make an unambiguous choice (e.g. 'Paris (fr-paris-01)'). The value is serialised as tcomp::action::suggestions::labelDisplayMode in the component metadata map by the existing ActionParameterEnricher, with no changes to component-server-model required. Also includes Spotless reformatting of several pre-existing violations caught during mvn spotless:apply, and a .gitignore update for ai-commons tooling files. Co-Authored-By: GitHub Copilot <ai@noreply> * fix(QTDI-2979): apply spotless * fix(QTDI-1979): take into account PR review comment --------- Co-authored-by: GitHub Copilot <ai@noreply>
1 parent 75d9593 commit e1cc0de

17 files changed

Lines changed: 167 additions & 65 deletions

File tree

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@ documentation/rebuildpdf.bat
6666
.envrc
6767
.env
6868
*.versionsBackup
69+
70+
# ai-commons knowledge base (managed by ai-commons/setup.sh)
71+
.ai-commons/
72+
.ai-commons/sessions/
73+
.ai-commons/skills/manage-zephyr-test-cases/references/.env
74+
.claude/CLAUDE.md
75+
.github/copilot-instructions.md

component-api/src/main/java/org/talend/sdk/component/api/configuration/action/Suggestable.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,32 @@
7474
* @return parameters for the validation.
7575
*/
7676
String[] parameters() default { "." };
77+
78+
/**
79+
* Controls how each suggestion option is rendered in the UI.
80+
* Use {@link LabelDisplayMode#LABEL_ID} when option labels may not be unique,
81+
* so that the identifier is shown alongside the label to allow unambiguous selection.
82+
* Defaults to {@link LabelDisplayMode#LABEL} — preserves full backward compatibility.
83+
*
84+
* @return the display mode for suggestion options.
85+
*/
86+
LabelDisplayMode labelDisplayMode() default LabelDisplayMode.LABEL;
87+
88+
/**
89+
* Defines how suggestion options are displayed in the UI.
90+
*/
91+
enum LabelDisplayMode {
92+
93+
/**
94+
* Display the option label only.
95+
* Default behaviour — use this when labels are unique.
96+
*/
97+
LABEL,
98+
99+
/**
100+
* Display both the label and the identifier, e.g. {@code Paris (fr-paris-01)}.
101+
* Use this when labels may not be unique so that the user can make an unambiguous choice.
102+
*/
103+
LABEL_ID
104+
}
77105
}

component-runtime-impl/src/main/java/org/talend/sdk/component/runtime/reflect/Defaults.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ public class Defaults {
6363
.invokeWithArguments(args);
6464
} else { // j > 8 - can need some --add-opens, we will add a module-info later to be clean when dropping j8
6565
final Method privateLookup = findPrivateLookup();
66-
HANDLER = (clazz, method, proxy, args) -> ((MethodHandles.Lookup) privateLookup.invoke(null, clazz, constructor.newInstance(clazz)))
67-
.unreflectSpecial(method, clazz)
68-
.bindTo(proxy)
69-
.invokeWithArguments(args);
66+
HANDLER = (clazz, method, proxy,
67+
args) -> ((MethodHandles.Lookup) privateLookup.invoke(null, clazz, constructor.newInstance(clazz)))
68+
.unreflectSpecial(method, clazz)
69+
.bindTo(proxy)
70+
.invokeWithArguments(args);
7071
}
7172
}
7273

component-runtime-impl/src/test/java/org/talend/sdk/component/runtime/record/PluralRecordExtension.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ private Jsonb getJsonb(Jsonb jsonb) {
5858
// create a Jsonb instance which is PojoJsonbProvider as in component-runtime-manager
5959
return (Jsonb) Proxy
6060
.newProxyInstance(Thread.currentThread().getContextClassLoader(),
61-
new Class<?>[]{Jsonb.class, PojoJsonbProvider.class}, (proxy, method, args) -> {
61+
new Class<?>[] { Jsonb.class, PojoJsonbProvider.class }, (proxy, method, args) -> {
6262
if (method.getDeclaringClass() == Supplier.class) {
6363
return jsonb;
6464
}

component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/ComponentManager.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,7 +1924,8 @@ public void onPartitionMapper(final Class<?> type, final PartitionMapper partiti
19241924
() -> {
19251925
final List<ParameterMeta> params = parameterModelService
19261926
.buildParameterMetas(constructor, getPackage(type),
1927-
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices().get(LocalConfiguration.class)));
1927+
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices()
1928+
.get(LocalConfiguration.class)));
19281929
if (infinite) {
19291930
if (partitionMapper.stoppable()) {
19301931
addInfiniteMapperBuiltInParameters(type, params);
@@ -1977,7 +1978,8 @@ public void onEmitter(final Class<?> type, final Emitter emitter) {
19771978
final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin,
19781979
() -> parameterModelService
19791980
.buildParameterMetas(constructor, getPackage(type),
1980-
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices().get(LocalConfiguration.class)))));
1981+
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices()
1982+
.get(LocalConfiguration.class)))));
19811983
final Function<Map<String, String>, Object[]> parameterFactory =
19821984
createParametersFactory(plugin, constructor, services.getServices(), parameterMetas);
19831985
final String name = of(emitter.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName);
@@ -2161,7 +2163,8 @@ public void onDriverRunner(final Class<?> type, final DriverRunner processor) {
21612163
final Supplier<List<ParameterMeta>> parameterMetas = lazy(() -> executeInContainer(plugin,
21622164
() -> parameterModelService
21632165
.buildParameterMetas(constructor, getPackage(type),
2164-
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices().get(LocalConfiguration.class)))));
2166+
new BaseParameterEnricher.Context((LocalConfiguration) services.getServices()
2167+
.get(LocalConfiguration.class)))));
21652168
final Function<Map<String, String>, Object[]> parameterFactory =
21662169
createParametersFactory(plugin, constructor, services.getServices(), parameterMetas);
21672170
final String name = of(processor.name()).filter(n -> !n.isEmpty()).orElseGet(type::getName);

component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ParameterModelService.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,10 +254,10 @@ private Stream<Annotation> getReflectionAnnotations(final Type genericType, fina
254254
.concat(Stream.of(annotations),
255255
// if a class concat its annotations
256256
genericType instanceof Class
257-
? getClassAnnotations(genericType, annotations)
258-
: (hasAClassFirstParameter(genericType) ? getClassAnnotations(
259-
((ParameterizedType) genericType).getActualTypeArguments()[0],
260-
annotations) : Stream.empty()));
257+
? getClassAnnotations(genericType, annotations)
258+
: (hasAClassFirstParameter(genericType) ? getClassAnnotations(
259+
((ParameterizedType) genericType).getActualTypeArguments()[0],
260+
annotations) : Stream.empty()));
261261
}
262262

263263
private boolean hasAClassFirstParameter(final Type genericType) {
@@ -297,7 +297,7 @@ private List<ParameterMeta> buildParametersMetas(final String name, final String
297297
}
298298
return Stream
299299
.concat(buildParametersMetas(name + ".key[${index}]", prefix + "key[${index}].",
300-
(Class) pt.getActualTypeArguments()[0], annotations, i18nPackages, ignoreI18n,
300+
(Class) pt.getActualTypeArguments()[0], annotations, i18nPackages, ignoreI18n,
301301
context).stream(),
302302
buildParametersMetas(name + ".value[${index}]", prefix + "value[${index}].",
303303
(Class) pt.getActualTypeArguments()[1], annotations, i18nPackages,

component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/reflect/ReflectionService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,11 @@ private Object createObject(final ClassLoader loader, final Function<Supplier<Ob
524524
if (arrayClass.isArray()) {
525525
// we could use Array.newInstance but for now use the list, shouldn't impact
526526
// much the perf
527-
final Collection<?> list = (Collection) createList(loader, contextualSupplier, prefix + enclosingName, List.class,
528-
arrayClass.getComponentType(), toList(), createObjectFactory(loader,
529-
contextualSupplier, arrayClass.getComponentType(), metas, precomputed),
530-
new HashMap<>(listEntries), metas, precomputed);
527+
final Collection<?> list =
528+
(Collection) createList(loader, contextualSupplier, prefix + enclosingName, List.class,
529+
arrayClass.getComponentType(), toList(), createObjectFactory(loader,
530+
contextualSupplier, arrayClass.getComponentType(), metas, precomputed),
531+
new HashMap<>(listEntries), metas, precomputed);
531532

532533
// we need that conversion to ensure the type matches
533534
final Object array = Array.newInstance(arrayClass.getComponentType(), list.size());

component-runtime-manager/src/main/java/org/talend/sdk/component/runtime/manager/service/InjectorImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ private <T> void doInject(final Class<?> type, final T instance) {
128128
})
129129
.forEach(field -> {
130130
try {
131-
final Class<?> configClass = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
131+
final Class<?> configClass =
132+
(Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
132133
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
133134
final Supplier<?> supplier = () -> {
134135
try {

component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ComponentManagerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,8 @@ void testLocalConfigurationFromEnvironment(@TempDir final File temporaryFolder)
611611
manager.addPlugin(plugin.getAbsolutePath());
612612
final Container container = manager.getContainer().findAll().stream().findFirst().orElse(null);
613613
assertNotNull(container);
614-
final LocalConfiguration envConf = (LocalConfiguration) container.get(AllServices.class).getServices().get(LocalConfiguration.class);
614+
final LocalConfiguration envConf =
615+
(LocalConfiguration) container.get(AllServices.class).getServices().get(LocalConfiguration.class);
615616
// check translated env vars
616617
assertEquals("/home/user", envConf.get("USER_PATH"));
617618
assertEquals("/home/user", envConf.get("USER.PATH"));

component-runtime-manager/src/test/java/org/talend/sdk/component/runtime/manager/ConfigurationMigrationTest.java

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,30 @@ void run(@TempDir final Path temporaryFolder) throws Exception {
4242
"META-INF/test/dependencies", "org.talend.test:type=plugin,value=%s")) {
4343
manager.addPlugin(jar.getAbsolutePath());
4444
{
45-
final Object nested = ((ProcessorImpl) manager.findProcessor("chain", "configured1", 0, new HashMap<String, String>() {
45+
final Object nested = ((ProcessorImpl) manager
46+
.findProcessor("chain", "configured1", 0, new HashMap<String, String>() {
4647

47-
{
48-
put("config.__version", "-1");
49-
}
50-
}).orElseThrow(IllegalStateException::new))
48+
{
49+
put("config.__version", "-1");
50+
}
51+
})
52+
.orElseThrow(IllegalStateException::new))
5153
.getDelegate();
5254

5355
final Object config = get(nested, "getConfig");
5456
assertNotNull(config);
5557
assertEquals("ok", get(config, "getName"));
5658
}
5759
{
58-
final Object nested = ((ProcessorImpl) manager.findProcessor("chain", "configured2", 0, new HashMap<String, String>() {
60+
final Object nested = ((ProcessorImpl) manager
61+
.findProcessor("chain", "configured2", 0, new HashMap<String, String>() {
5962

60-
{
61-
put("config.__version", "0");
62-
put("value.__version", "-1");
63-
}
64-
}).orElseThrow(IllegalStateException::new))
63+
{
64+
put("config.__version", "0");
65+
put("value.__version", "-1");
66+
}
67+
})
68+
.orElseThrow(IllegalStateException::new))
6569
.getDelegate();
6670
assertEquals("set", get(nested, "getValue"));
6771

@@ -70,13 +74,15 @@ void run(@TempDir final Path temporaryFolder) throws Exception {
7074
assertEquals("ok", get(config, "getName"));
7175
}
7276
{
73-
final Object nested = ((ProcessorImpl) manager.findProcessor("chain", "migrationtest", -1, new HashMap<String, String>() {
77+
final Object nested = ((ProcessorImpl) manager
78+
.findProcessor("chain", "migrationtest", -1, new HashMap<String, String>() {
7479

75-
{
76-
put("config.__version", "1");
77-
put("config.datastore.__version", "1");
78-
}
79-
}).orElseThrow(IllegalStateException::new))
80+
{
81+
put("config.__version", "1");
82+
put("config.datastore.__version", "1");
83+
}
84+
})
85+
.orElseThrow(IllegalStateException::new))
8086
.getDelegate();
8187

8288
final Object config = get(nested, "getConfig");

0 commit comments

Comments
 (0)