This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/master by this push:
new 6edb39c1a2 feat(research): short, human-readable experiment ids
(EXP-XXXXXXXX) (#694)
6edb39c1a2 is described below
commit 6edb39c1a26fd2bcc9e454162a562811a03cd32a
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 15 00:29:17 2026 -0400
feat(research): short, human-readable experiment ids (EXP-XXXXXXXX) (#694)
Experiment ids were sanitize(name)[:50] + "_" + UUID, e.g.
Echo_on_Jun_14,_2026_9:36_PM_96aa3cbd-..., which mashed the name and a uuid
into
the primary key and the portal URL. Generate a short, URL-safe,
collision-checked
code instead (e.g. EXP-7QK2F9MX) via AiravataUtils.getReadableId, keeping
the name
as the separate experiment_name field. Only new experiments are affected;
the id
stays a varchar PK so existing ids and all FK references keep working. The
portal
already routes by experiment_id, so URLs become
/workspace/experiments/EXP-7QK2F9MX/
with no portal change.
The two tests that asserted the id embeds the normalized name now assert
the new
contract: a short EXP- code, with the experiment name preserved verbatim.
---
.../research/repository/ExperimentRepository.java | 10 +++++++---
.../ExperimentRepositoryIntegrationTest.java | 20 ++++++++++++--------
.../repository/ExperimentRepositoryTest.java | 15 +++++++++++----
.../java/org/apache/airavata/util/AiravataUtils.java | 20 ++++++++++++++++++++
4 files changed, 50 insertions(+), 15 deletions(-)
diff --git
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/ExperimentRepository.java
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/ExperimentRepository.java
index 50c46f2a9e..7d169cb035 100644
---
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/ExperimentRepository.java
+++
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/ExperimentRepository.java
@@ -164,11 +164,15 @@ public class ExperimentRepository extends
AbstractRepository<ExperimentModel, Ex
.setState(ExperimentState.EXPERIMENT_STATE_CREATED)
.setTimeOfStateChange(AiravataUtils.getCurrentTimestamp().getTime())
.build();
- String expName = experimentModel.getExperimentName();
- // This is to avoid overflow of experiment id size. Total experiment
id length is <= 50 + UUID
+ // experiment_id is a short, human-readable, URL-safe code (e.g.
EXP-7QK2F9MX). The experiment
+ // name is kept separately in experiment_name. Regenerate on the rare
collision since it's the PK.
+ String experimentId;
+ do {
+ experimentId = AiravataUtils.getReadableId("EXP");
+ } while (isExperimentExist(experimentId));
experimentModel = experimentModel.toBuilder()
.addExperimentStatus(experimentStatus)
- .setExperimentId(AiravataUtils.getId(expName.substring(0,
Math.min(expName.length(), 50))))
+ .setExperimentId(experimentId)
.build();
return saveExperimentModelData(experimentModel);
diff --git
a/airavata-api/research-service/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
b/airavata-api/research-service/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
index b6387b315f..d353108faa 100644
---
a/airavata-api/research-service/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
+++
b/airavata-api/research-service/src/test/java/org/apache/airavata/integration/ExperimentRepositoryIntegrationTest.java
@@ -210,20 +210,24 @@ public class ExperimentRepositoryIntegrationTest extends
TestBase {
@Test
@Order(4)
- @DisplayName("Slashes in experiment name are replaced with underscores in
generated id")
- void slashesInNameAreNormalized() throws Exception {
+ @DisplayName("experiment_id is a short readable code and the experiment
name is preserved verbatim")
+ void experimentIdIsShortCodeAndNamePreserved() throws Exception {
ExperimentModel experiment = buildExperiment("name/forward-slash//a");
String experimentId = experimentRepository.addExperiment(experiment);
- assertTrue(
- experimentId.startsWith("name_forward-slash__a"),
- "forward slashes should be replaced with underscores");
+ assertTrue(experimentId.startsWith("EXP-"), "experiment id should be a
short EXP- code");
+ assertEquals(
+ "name/forward-slash//a",
+
experimentRepository.getExperiment(experimentId).getExperimentName(),
+ "experiment name should be preserved verbatim");
experimentRepository.removeExperiment(experimentId);
experiment = buildExperiment("name\\backward-slash\\\\a");
experimentId = experimentRepository.addExperiment(experiment);
- assertTrue(
- experimentId.startsWith("name_backward-slash__a"),
- "backward slashes should be replaced with underscores");
+ assertTrue(experimentId.startsWith("EXP-"), "experiment id should be a
short EXP- code");
+ assertEquals(
+ "name\\backward-slash\\\\a",
+
experimentRepository.getExperiment(experimentId).getExperimentName(),
+ "experiment name should be preserved verbatim");
experimentRepository.removeExperiment(experimentId);
}
}
diff --git
a/airavata-api/research-service/src/test/java/org/apache/airavata/research/repository/ExperimentRepositoryTest.java
b/airavata-api/research-service/src/test/java/org/apache/airavata/research/repository/ExperimentRepositoryTest.java
index 81243f8bde..ea5ea8f6d6 100644
---
a/airavata-api/research-service/src/test/java/org/apache/airavata/research/repository/ExperimentRepositoryTest.java
+++
b/airavata-api/research-service/src/test/java/org/apache/airavata/research/repository/ExperimentRepositoryTest.java
@@ -306,10 +306,11 @@ public class ExperimentRepositoryTest extends TestBase {
}
/**
- * Verify that slashes (forward and backward) are replaced with
underscores.
+ * The generated experiment id is a short, readable code (independent of
the name), and the
+ * experiment name is preserved verbatim (slashes are no longer mangled
into the id).
*/
@Test
- public void testSlashesInExperimentName() throws RegistryException {
+ public void testGeneratedExperimentIdAndNamePreservation() throws
RegistryException {
// Forward slashes
ExperimentModel experimentModel =
@@ -327,7 +328,10 @@ public class ExperimentRepositoryTest extends TestBase {
.build();
String experimentId =
experimentRepository.addExperiment(experimentModel);
- assertTrue(experimentId.startsWith("name_forward-slash__a"));
+ assertTrue(experimentId.startsWith("EXP-"));
+ assertEquals(
+ "name/forward-slash//a",
+
experimentRepository.getExperiment(experimentId).getExperimentName());
// Backward slashes
experimentModel =
ExperimentModel.newBuilder().setProjectId(projectId).build();
@@ -344,6 +348,9 @@ public class ExperimentRepositoryTest extends TestBase {
.build();
experimentId = experimentRepository.addExperiment(experimentModel);
- assertTrue(experimentId.startsWith("name_backward-slash__a"));
+ assertTrue(experimentId.startsWith("EXP-"));
+ assertEquals(
+ "name\\backward-slash\\\\a",
+
experimentRepository.getExperiment(experimentId).getExperimentName());
}
}
diff --git
a/airavata-api/src/main/java/org/apache/airavata/util/AiravataUtils.java
b/airavata-api/src/main/java/org/apache/airavata/util/AiravataUtils.java
index 1f6631fe74..0a3dea54d8 100644
--- a/airavata-api/src/main/java/org/apache/airavata/util/AiravataUtils.java
+++ b/airavata-api/src/main/java/org/apache/airavata/util/AiravataUtils.java
@@ -19,12 +19,19 @@
*/
package org.apache.airavata.util;
+import java.security.SecureRandom;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.UUID;
public class AiravataUtils {
+ // Unambiguous Crockford-style base32 alphabet (no 0/1/I/L/O) for short,
human-readable,
+ // URL-safe ids.
+ private static final char[] READABLE_ID_ALPHABET =
"23456789ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray();
+ private static final int READABLE_ID_LENGTH = 8;
+ private static final SecureRandom READABLE_ID_RANDOM = new SecureRandom();
+
public static Timestamp getCurrentTimestamp() {
Calendar calender = Calendar.getInstance();
// java.util.Date d = calender.getTimeInMillis();
@@ -42,4 +49,17 @@ public class AiravataUtils {
String id = name.trim().replaceAll("\\s|\\.|/|\\\\", "_");
return id + "_" + UUID.randomUUID();
}
+
+ /**
+ * Generate a short, human-readable, URL-safe id of the form {@code
<prefix>-XXXXXXXX} using an
+ * unambiguous alphabet (no 0/1/I/L/O). The id carries no semantic
meaning; callers that use it as
+ * a primary key should regenerate it on the (vanishingly rare) collision.
+ */
+ public static String getReadableId(String prefix) {
+ StringBuilder sb = new StringBuilder(prefix).append('-');
+ for (int i = 0; i < READABLE_ID_LENGTH; i++) {
+
sb.append(READABLE_ID_ALPHABET[READABLE_ID_RANDOM.nextInt(READABLE_ID_ALPHABET.length)]);
+ }
+ return sb.toString();
+ }
}