From 285327348a323292cf02fbc0dfc230dd30307567 Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Thu, 21 May 2026 11:20:46 +0100 Subject: [PATCH 1/6] feat: extend datasets commands visibility, labels, filter support --- .../tower/cli/commands/DatasetsCmd.java | 6 + .../datasets/AbstractDatasetsCmd.java | 38 ++-- .../datasets/DatasetMultiRefOptions.java | 36 ++++ .../datasets/DatasetsLabelsManager.java | 51 +++++ .../tower/cli/commands/datasets/HideCmd.java | 51 +++++ .../cli/commands/datasets/LabelsCmd.java | 43 ++++ .../tower/cli/commands/datasets/ListCmd.java | 39 +++- .../tower/cli/commands/datasets/ShowCmd.java | 51 +++++ .../tower/cli/commands/runs/ListCmd.java | 2 +- .../cli/responses/datasets/DatasetList.java | 35 +++- .../datasets/DatasetsVisibility.java | 43 ++++ .../tower/cli/datasets/DatasetsCmdTest.java | 185 +++++++++++++++++- 12 files changed, 548 insertions(+), 32 deletions(-) create mode 100644 src/main/java/io/seqera/tower/cli/commands/datasets/DatasetMultiRefOptions.java create mode 100644 src/main/java/io/seqera/tower/cli/commands/datasets/DatasetsLabelsManager.java create mode 100644 src/main/java/io/seqera/tower/cli/commands/datasets/HideCmd.java create mode 100644 src/main/java/io/seqera/tower/cli/commands/datasets/LabelsCmd.java create mode 100644 src/main/java/io/seqera/tower/cli/commands/datasets/ShowCmd.java create mode 100644 src/main/java/io/seqera/tower/cli/responses/datasets/DatasetsVisibility.java diff --git a/src/main/java/io/seqera/tower/cli/commands/DatasetsCmd.java b/src/main/java/io/seqera/tower/cli/commands/DatasetsCmd.java index f9bb73d0..fbb568f4 100644 --- a/src/main/java/io/seqera/tower/cli/commands/DatasetsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/DatasetsCmd.java @@ -20,7 +20,10 @@ import io.seqera.tower.cli.commands.datasets.AddCmd; import io.seqera.tower.cli.commands.datasets.DeleteCmd; import io.seqera.tower.cli.commands.datasets.DownloadCmd; +import io.seqera.tower.cli.commands.datasets.HideCmd; +import io.seqera.tower.cli.commands.datasets.LabelsCmd; import io.seqera.tower.cli.commands.datasets.ListCmd; +import io.seqera.tower.cli.commands.datasets.ShowCmd; import io.seqera.tower.cli.commands.datasets.UpdateCmd; import io.seqera.tower.cli.commands.datasets.UrlCmd; import io.seqera.tower.cli.commands.datasets.ViewCmd; @@ -33,7 +36,10 @@ AddCmd.class, DeleteCmd.class, DownloadCmd.class, + HideCmd.class, + LabelsCmd.class, ListCmd.class, + ShowCmd.class, ViewCmd.class, UpdateCmd.class, UrlCmd.class, diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java index a1b752dd..7dabc86e 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java @@ -26,6 +26,7 @@ import io.seqera.tower.model.ListDatasetsResponse; import picocli.CommandLine; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -49,29 +50,7 @@ protected DatasetDto datasetByName(Long workspaceId, String datasetName) throws throw new DatasetNotFoundException(datasetName, workspaceRef(workspaceId)); } - return datasetList.stream().findFirst().orElse(null); - } - - protected List searchByName(Long workspaceId, String datasetName) throws ApiException { - ListDatasetsResponse listDatasetsResponse = datasetsApi().listDatasets(workspaceId); - - if (datasetName == null) { - return listDatasetsResponse.getDatasets(); - } - - if (listDatasetsResponse == null || listDatasetsResponse.getDatasets() == null) { - throw new DatasetNotFoundException(workspaceRef(workspaceId)); - } - - List datasetList = listDatasetsResponse.getDatasets().stream() - .filter(it -> it.getName().startsWith(datasetName)) - .collect(Collectors.toList()); - - if (datasetList.isEmpty()) { - throw new DatasetNotFoundException(datasetName, workspaceRef(workspaceId)); - } - - return datasetList; + return datasetList.get(0); } protected DatasetDto fetchDescribeDatasetResponse(DatasetRefOptions datasetRefOptions, Long wspId) throws ApiException { @@ -129,6 +108,19 @@ protected String getDatasetRef(DatasetRefOptions datasetRefOptions) { return datasetRefOptions.dataset.datasetName != null ? datasetRefOptions.dataset.datasetName : datasetRefOptions.dataset.datasetId; } + protected List resolveDatasetIds(DatasetMultiRefOptions options, Long wspId) throws ApiException { + List ids = new ArrayList<>(); + if (options.dataset.datasetIds != null) { + ids.addAll(options.dataset.datasetIds); + } + if (options.dataset.datasetNames != null) { + for (String name : options.dataset.datasetNames) { + ids.add(datasetByName(wspId, name).getId()); + } + } + return ids; + } + protected void deleteDatasetByName(String datasetName, Long wspId) throws DatasetNotFoundException, ApiException { DatasetDto response = datasetByName(wspId, datasetName); deleteDatasetById(response.getId(), wspId); diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetMultiRefOptions.java b/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetMultiRefOptions.java new file mode 100644 index 00000000..e2a17ec8 --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetMultiRefOptions.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.commands.datasets; + +import picocli.CommandLine; + +import java.util.List; + +public class DatasetMultiRefOptions { + + @CommandLine.ArgGroup(multiplicity = "1") + public DatasetMultiRef dataset; + + public static class DatasetMultiRef { + + @CommandLine.Option(names = {"-i", "--id"}, arity = "1..*", description = "Dataset unique identifier(s). May be combined with --name.") + public List datasetIds; + + @CommandLine.Option(names = {"-n", "--name"}, arity = "1..*", description = "Dataset name(s). May be combined with --id.") + public List datasetNames; + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetsLabelsManager.java b/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetsLabelsManager.java new file mode 100644 index 00000000..fc6616ff --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/DatasetsLabelsManager.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.commands.datasets; + +import io.seqera.tower.ApiException; +import io.seqera.tower.api.LabelsApi; +import io.seqera.tower.cli.commands.labels.BaseLabelsManager; +import io.seqera.tower.model.AssociateDatasetsLabelsRequest; + +import java.util.List; + +public class DatasetsLabelsManager extends BaseLabelsManager { + + public DatasetsLabelsManager(LabelsApi api) { + super(api, "dataset"); + } + + @Override + protected AssociateDatasetsLabelsRequest getRequest(List labelsIds, String entityId) { + return new AssociateDatasetsLabelsRequest().labelIds(labelsIds).datasetIds(List.of(entityId)); + } + + @Override + protected void apply(AssociateDatasetsLabelsRequest request, Long wspId) throws ApiException { + api.applyLabelsToDatasets(request, wspId); + } + + @Override + protected void remove(AssociateDatasetsLabelsRequest request, Long wspId) throws ApiException { + api.removeLabelsFromDatasets(request, wspId); + } + + @Override + protected void append(AssociateDatasetsLabelsRequest request, Long wspId) throws ApiException { + api.addLabelsToDatasets(request, wspId); + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/HideCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/HideCmd.java new file mode 100644 index 00000000..afdda04b --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/HideCmd.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.commands.datasets; + +import io.seqera.tower.ApiException; +import io.seqera.tower.cli.commands.global.WorkspaceRequiredOptions; +import io.seqera.tower.cli.responses.Response; +import io.seqera.tower.cli.responses.datasets.DatasetsVisibility; +import io.seqera.tower.model.ChangeDatasetVisibilityRequest; +import picocli.CommandLine; + +import java.io.IOException; +import java.util.List; + +@CommandLine.Command( + name = "hide", + description = "Hide one or more datasets" +) +public class HideCmd extends AbstractDatasetsCmd { + + @CommandLine.Mixin + public DatasetMultiRefOptions datasetMultiRefOptions; + + @CommandLine.Mixin + public WorkspaceRequiredOptions workspace; + + @Override + protected Response exec() throws ApiException, IOException { + Long wspId = workspaceId(workspace.workspace); + List ids = resolveDatasetIds(datasetMultiRefOptions, wspId); + + ChangeDatasetVisibilityRequest request = new ChangeDatasetVisibilityRequest().datasetIds(ids); + datasetsApi().hideDatasets(request, wspId); + + return new DatasetsVisibility(ids, workspace.workspace, true); + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/LabelsCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/LabelsCmd.java new file mode 100644 index 00000000..2a803135 --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/LabelsCmd.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.commands.datasets; + +import io.seqera.tower.ApiException; +import io.seqera.tower.cli.commands.labels.LabelsSubcmdOptions; +import io.seqera.tower.cli.responses.Response; +import picocli.CommandLine; + +import java.io.IOException; + +@CommandLine.Command(name = "labels", description = "Manage dataset labels") +public class LabelsCmd extends AbstractDatasetsCmd { + + @CommandLine.Mixin + public DatasetRefOptions datasetRefOptions; + + @CommandLine.Mixin + public LabelsSubcmdOptions labelsSubcmdOptions; + + @Override + protected Response exec() throws ApiException, IOException { + Long wspId = workspaceId(labelsSubcmdOptions.workspace.workspace); + String datasetId = fetchDescribeDatasetResponse(datasetRefOptions, wspId).getId(); + + DatasetsLabelsManager manager = new DatasetsLabelsManager(labelsApi()); + return manager.execute(wspId, datasetId, labelsSubcmdOptions); + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java index fb390f78..60ac7403 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java @@ -17,13 +17,18 @@ package io.seqera.tower.cli.commands.datasets; import io.seqera.tower.ApiException; +import io.seqera.tower.cli.commands.global.PaginationOptions; +import io.seqera.tower.cli.commands.global.ShowLabelsOption; import io.seqera.tower.cli.commands.global.WorkspaceRequiredOptions; import io.seqera.tower.cli.responses.Response; import io.seqera.tower.cli.responses.datasets.DatasetList; -import io.seqera.tower.model.DatasetDto; +import io.seqera.tower.cli.utils.PaginationInfo; +import io.seqera.tower.model.DatasetQueryAttribute; +import io.seqera.tower.model.ListDatasetsResponse; import picocli.CommandLine; import java.io.IOException; +import java.util.Collections; import java.util.List; @CommandLine.Command( @@ -35,14 +40,40 @@ public class ListCmd extends AbstractDatasetsCmd { @CommandLine.Mixin public WorkspaceRequiredOptions workspace; - @CommandLine.Option(names = {"-f", "--filter"}, description = "Filter datasets by name substring") + @CommandLine.Option(names = {"-f", "--filter"}, description = "Search expression passed to the server. Matches dataset name and other server-supported keys.") public String filter; + @CommandLine.Option(names = {"--show-hidden"}, description = "Include datasets marked as hidden in the results.", defaultValue = "false") + public boolean showHidden; + + @CommandLine.Mixin + ShowLabelsOption showLabelsOption; + + @CommandLine.Mixin + PaginationOptions paginationOptions; + @Override protected Response exec() throws ApiException, IOException { Long wspId = workspaceId(workspace.workspace); - List response = searchByName(wspId, filter); - return new DatasetList(response, workspace.workspace); + Integer max = PaginationOptions.getMax(paginationOptions); + Integer offset = PaginationOptions.getOffset(paginationOptions, max); + + List attributes = Boolean.TRUE.equals(showLabelsOption.showLabels) + ? List.of(DatasetQueryAttribute.labels) + : Collections.emptyList(); + + String visibility = showHidden ? "all" : null; + + ListDatasetsResponse response = datasetsApi().listDatasetsV2( + wspId, max, offset, filter, null, null, visibility, attributes + ); + + return new DatasetList( + response.getDatasets(), + workspace.workspace, + Boolean.TRUE.equals(showLabelsOption.showLabels), + PaginationInfo.from(paginationOptions, response.getTotalSize()) + ); } } diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/ShowCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/ShowCmd.java new file mode 100644 index 00000000..124c267a --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/ShowCmd.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.commands.datasets; + +import io.seqera.tower.ApiException; +import io.seqera.tower.cli.commands.global.WorkspaceRequiredOptions; +import io.seqera.tower.cli.responses.Response; +import io.seqera.tower.cli.responses.datasets.DatasetsVisibility; +import io.seqera.tower.model.ChangeDatasetVisibilityRequest; +import picocli.CommandLine; + +import java.io.IOException; +import java.util.List; + +@CommandLine.Command( + name = "show", + description = "Make one or more hidden datasets visible" +) +public class ShowCmd extends AbstractDatasetsCmd { + + @CommandLine.Mixin + public DatasetMultiRefOptions datasetMultiRefOptions; + + @CommandLine.Mixin + public WorkspaceRequiredOptions workspace; + + @Override + protected Response exec() throws ApiException, IOException { + Long wspId = workspaceId(workspace.workspace); + List ids = resolveDatasetIds(datasetMultiRefOptions, wspId); + + ChangeDatasetVisibilityRequest request = new ChangeDatasetVisibilityRequest().datasetIds(ids); + datasetsApi().showDatasets(request, wspId); + + return new DatasetsVisibility(ids, workspace.workspace, false); + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/runs/ListCmd.java b/src/main/java/io/seqera/tower/cli/commands/runs/ListCmd.java index f864f490..ec05f615 100644 --- a/src/main/java/io/seqera/tower/cli/commands/runs/ListCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/runs/ListCmd.java @@ -40,7 +40,7 @@ public class ListCmd extends AbstractRunsCmd { @CommandLine.Mixin public WorkspaceOptionalOptions workspace; - @CommandLine.Option(names = {"-f", "--filter"}, description = "Filter pipeline runs by run name. Performs case-insensitive substring matching on the runName field.") + @CommandLine.Option(names = {"-f", "--filter"}, description = "Filter pipeline runs using the server search syntax. A bare value matches the run name substring; key:value pairs filter on supported keys (e.g. datasetId:, runName:, status:, after:, before:).") public String filter; @CommandLine.Mixin diff --git a/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java index 8702bb91..5d8bf002 100644 --- a/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java +++ b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java @@ -16,38 +16,67 @@ package io.seqera.tower.cli.responses.datasets; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.seqera.tower.cli.responses.Response; import io.seqera.tower.cli.utils.FormatHelper; +import io.seqera.tower.cli.utils.PaginationInfo; import io.seqera.tower.cli.utils.TableList; import io.seqera.tower.model.DatasetDto; +import jakarta.annotation.Nullable; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; public class DatasetList extends Response { public final List datasetList; public final String workspace; + public final boolean showLabels; + + @JsonIgnore + @Nullable + private final PaginationInfo paginationInfo; public DatasetList(List datasetList, String workspace) { + this(datasetList, workspace, false, null); + } + + public DatasetList(List datasetList, String workspace, boolean showLabels, @Nullable PaginationInfo paginationInfo) { this.datasetList = datasetList; this.workspace = workspace; + this.showLabels = showLabels; + this.paginationInfo = paginationInfo; } @Override public void toString(PrintWriter out) { out.println(ansi(String.format("%n @|bold Datasets at %s workspace:|@%n", workspace))); - if (datasetList.isEmpty()) { + if (datasetList == null || datasetList.isEmpty()) { out.println(ansi(" @|yellow No datasets found|@")); return; } - TableList table = new TableList(out, 3, "ID", "Name", "Created").sortBy(0); + List desc = new ArrayList<>(List.of("ID", "Name", "Created", "Hidden")); + if (showLabels) desc.add("Labels"); + + TableList table = new TableList(out, desc.size(), desc.toArray(new String[0])).sortBy(0); table.setPrefix(" "); - datasetList.forEach(ds -> table.addRow(ds.getId(), ds.getName(), FormatHelper.formatDate(ds.getDateCreated()))); + datasetList.forEach(ds -> { + List row = new ArrayList<>(List.of( + ds.getId(), + ds.getName(), + FormatHelper.formatDate(ds.getDateCreated()), + Boolean.TRUE.equals(ds.getHidden()) ? "yes" : "no" + )); + if (showLabels) row.add(FormatHelper.formatLabels(ds.getLabels())); + table.addRow(row.toArray(new String[0])); + }); table.print(); + PaginationInfo.addFooter(out, paginationInfo); + out.println(""); } } diff --git a/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetsVisibility.java b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetsVisibility.java new file mode 100644 index 00000000..2d0965d8 --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetsVisibility.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021-2026, Seqera. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.seqera.tower.cli.responses.datasets; + +import io.seqera.tower.cli.responses.Response; + +import java.io.PrintWriter; +import java.util.List; + +public class DatasetsVisibility extends Response { + + public final List datasetIds; + public final String workspace; + public final boolean hidden; + + public DatasetsVisibility(List datasetIds, String workspace, boolean hidden) { + this.datasetIds = datasetIds; + this.workspace = workspace; + this.hidden = hidden; + } + + @Override + public void toString(PrintWriter out) { + String action = hidden ? "hidden" : "shown"; + out.println(ansi(String.format("%n @|yellow Datasets %s at %s workspace:|@", action, workspace))); + datasetIds.forEach(id -> out.println(ansi(String.format(" - %s", id)))); + out.println(""); + } +} diff --git a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java index 46415904..30679ad3 100644 --- a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java @@ -28,6 +28,8 @@ import io.seqera.tower.cli.responses.datasets.DatasetUrl; import io.seqera.tower.cli.responses.datasets.DatasetVersionsList; import io.seqera.tower.cli.responses.datasets.DatasetView; +import io.seqera.tower.cli.responses.datasets.DatasetsVisibility; +import io.seqera.tower.cli.responses.labels.ManageLabels; import io.seqera.tower.model.DatasetDto; import io.seqera.tower.model.DatasetVersionDto; import org.junit.jupiter.params.ParameterizedTest; @@ -41,12 +43,14 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; import static io.seqera.tower.cli.utils.JsonHelper.parseJson; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.mockserver.model.JsonBody.json; public class DatasetsCmdTest extends BaseCmdTest { @@ -55,7 +59,8 @@ public class DatasetsCmdTest extends BaseCmdTest { void testList(OutputType format, MockServerClient mock) throws JsonProcessingException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); @@ -373,4 +378,182 @@ void testFileNotExistsError(OutputType format, MockServerClient mock) throws IOE assertEquals("", out.stdOut); assertEquals(1, out.exitCode); } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testListWithFilter(OutputType format, MockServerClient mock) throws JsonProcessingException { + + mock.when( + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293") + .withQueryStringParameter("search", "data"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "datasets", "list", "-w", "249664655368293", "--filter", "data"); + + assertOutput(format, out, new DatasetList(Arrays.asList( + parseJson("{\"id\":\"4D9TP0w2pM0qmwqVHgrgBK\",\"name\":\"dataset1\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-26T14:51:20+01:00\",\"lastUpdated\":\"2021-11-26T14:51:20+01:00\"}", DatasetDto.class), + parseJson("{\"id\":\"1W2FqBiI6WoNokQTkPkEzo\",\"name\":\"dataset2\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-29T08:05:44+01:00\",\"lastUpdated\":\"2021-11-29T08:05:44+01:00\"}", DatasetDto.class) + ), "249664655368293")); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testListShowHidden(OutputType format, MockServerClient mock) throws JsonProcessingException { + + mock.when( + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293") + .withQueryStringParameter("visibility", "all"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "datasets", "list", "-w", "249664655368293", "--show-hidden"); + + assertOutput(format, out, new DatasetList(Arrays.asList( + parseJson("{\"id\":\"4D9TP0w2pM0qmwqVHgrgBK\",\"name\":\"dataset1\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-26T14:51:20+01:00\",\"lastUpdated\":\"2021-11-26T14:51:20+01:00\"}", DatasetDto.class), + parseJson("{\"id\":\"1W2FqBiI6WoNokQTkPkEzo\",\"name\":\"dataset2\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-29T08:05:44+01:00\",\"lastUpdated\":\"2021-11-29T08:05:44+01:00\"}", DatasetDto.class) + ), "249664655368293")); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testHideById(OutputType format, MockServerClient mock) { + mock.when( + request().withMethod("POST").withPath("/datasets/hide") + .withQueryStringParameter("workspaceId", "249664655368293") + .withBody(json("{\"datasetIds\":[\"4D9TP0w2pM0qmwqVHgrgBK\",\"1W2FqBiI6WoNokQTkPkEzo\"]}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "datasets", "hide", "-w", "249664655368293", + "-i", "4D9TP0w2pM0qmwqVHgrgBK", "-i", "1W2FqBiI6WoNokQTkPkEzo"); + + assertOutput(format, out, new DatasetsVisibility( + List.of("4D9TP0w2pM0qmwqVHgrgBK", "1W2FqBiI6WoNokQTkPkEzo"), + "249664655368293", true)); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testShowById(OutputType format, MockServerClient mock) { + mock.when( + request().withMethod("POST").withPath("/datasets/show") + .withQueryStringParameter("workspaceId", "249664655368293") + .withBody(json("{\"datasetIds\":[\"4D9TP0w2pM0qmwqVHgrgBK\"]}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "datasets", "show", "-w", "249664655368293", + "-i", "4D9TP0w2pM0qmwqVHgrgBK"); + + assertOutput(format, out, new DatasetsVisibility( + List.of("4D9TP0w2pM0qmwqVHgrgBK"), + "249664655368293", false)); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testHideByName(OutputType format, MockServerClient mock) { + mock.when( + request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("POST").withPath("/datasets/hide") + .withQueryStringParameter("workspaceId", "249664655368293") + .withBody(json("{\"datasetIds\":[\"4D9TP0w2pM0qmwqVHgrgBK\"]}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "datasets", "hide", "-w", "249664655368293", + "-n", "dataset1"); + + assertOutput(format, out, new DatasetsVisibility( + List.of("4D9TP0w2pM0qmwqVHgrgBK"), + "249664655368293", true)); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testShowByName(OutputType format, MockServerClient mock) { + mock.when( + request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("POST").withPath("/datasets/show") + .withQueryStringParameter("workspaceId", "249664655368293") + .withBody(json("{\"datasetIds\":[\"4D9TP0w2pM0qmwqVHgrgBK\"]}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "datasets", "show", "-w", "249664655368293", + "-n", "dataset1"); + + assertOutput(format, out, new DatasetsVisibility( + List.of("4D9TP0w2pM0qmwqVHgrgBK"), + "249664655368293", false)); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testLabelsApply(OutputType format, MockServerClient mock) { + mock.when( + request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/labels") + .withQueryStringParameter("search", "foo"), exactly(1) + ).respond( + response().withStatusCode(200).withContentType(MediaType.APPLICATION_JSON) + .withBody("{\"labels\":[{\"id\":111,\"name\":\"foo\",\"value\":null,\"resource\":false,\"isDefault\":false}],\"totalSize\":1}") + ); + + mock.when( + request().withMethod("POST").withPath("/datasets/labels/apply") + .withQueryStringParameter("workspaceId", "249664655368293") + .withBody(json("{\"datasetIds\":[\"4D9TP0w2pM0qmwqVHgrgBK\"],\"labelIds\":[111]}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "datasets", "labels", "-w", "249664655368293", + "-i", "4D9TP0w2pM0qmwqVHgrgBK", "foo"); + + assertOutput(format, out, new ManageLabels("set", "dataset", "4D9TP0w2pM0qmwqVHgrgBK", 249664655368293L)); + assertEquals("", out.stdErr); + assertEquals(0, out.exitCode); + } } From 763a8f2e4b945a02beea682c0c344776945b18be Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Thu, 21 May 2026 19:42:33 +0100 Subject: [PATCH 2/6] chore: update datasets commands to use V2 dataset api --- .../datasets/AbstractDatasetsCmd.java | 8 +- .../tower/cli/commands/datasets/AddCmd.java | 4 +- .../cli/commands/datasets/UpdateCmd.java | 4 +- .../datasets/versions/VersionsCmd.java | 2 +- .../tower/cli/datasets/DatasetsCmdTest.java | 78 ++++++++++++------- 5 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java index 7dabc86e..6806dab2 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java @@ -36,7 +36,7 @@ public abstract class AbstractDatasetsCmd extends AbstractApiCmd { protected DatasetDto datasetByName(Long workspaceId, String datasetName) throws ApiException { - ListDatasetsResponse listDatasetsResponse = datasetsApi().listDatasets(workspaceId); + ListDatasetsResponse listDatasetsResponse = datasetsApi().listDatasetsV2(workspaceId, null, null, datasetName, null, null, null, List.of()); if (listDatasetsResponse == null || listDatasetsResponse.getDatasets() == null) { throw new DatasetNotFoundException(workspaceRef(workspaceId)); @@ -57,7 +57,7 @@ protected DatasetDto fetchDescribeDatasetResponse(DatasetRefOptions datasetRefOp DatasetDto response; if (datasetRefOptions.dataset.datasetId != null) { - response = datasetsApi().describeDataset(wspId, datasetRefOptions.dataset.datasetId, List.of()).getDataset(); + response = datasetsApi().describeDatasetV2(datasetRefOptions.dataset.datasetId, wspId, List.of()).getDataset(); } else { response = datasetByName(wspId, datasetRefOptions.dataset.datasetName); } @@ -68,7 +68,7 @@ protected DatasetDto fetchDescribeDatasetResponse(DatasetRefOptions datasetRefOp protected DatasetVersionDto fetchDatasetVersion(Long wspId, String datasetId, String datasetMediaType, Long version) throws ApiException { DatasetVersionDto datasetVersion; - ListDatasetVersionsResponse listDatasetVersionsResponse = datasetsApi().listDatasetVersions(wspId, datasetId, datasetMediaType); + ListDatasetVersionsResponse listDatasetVersionsResponse = datasetsApi().listDatasetVersionsV2(datasetId, wspId, datasetMediaType); if (listDatasetVersionsResponse == null || listDatasetVersionsResponse.getVersions() == null) { throw new TowerException(String.format("No versions were found for dataset %s", datasetId)); @@ -127,7 +127,7 @@ protected void deleteDatasetByName(String datasetName, Long wspId) throws Datase } protected void deleteDatasetById(String datasetId, Long wspId) throws DatasetNotFoundException, ApiException { - datasetsApi().deleteDataset(wspId, datasetId); + datasetsApi().deleteDatasetV2(datasetId, wspId); } diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/AddCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/AddCmd.java index 3070c12d..fa172980 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/AddCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/AddCmd.java @@ -72,9 +72,9 @@ protected Response exec() throws ApiException, IOException { if (overwrite) tryDeleteDataset(name, wspId); - CreateDatasetResponse response = datasetsApi().createDataset(wspId, request); + CreateDatasetResponse response = datasetsApi().createDatasetV2(request, wspId); - datasetsApi().uploadDataset(wspId, response.getDataset().getId(), header, fileName.toFile()); + datasetsApi().uploadDatasetV2(response.getDataset().getId(), wspId, header, fileName.toFile()); return new DatasetCreate(response.getDataset().getName(), workspace.workspace, response.getDataset().getId()); } diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/UpdateCmd.java index ab6b4dbf..60a3bef8 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/UpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/UpdateCmd.java @@ -64,10 +64,10 @@ protected Response exec() throws ApiException, IOException { request.setName(newName != null ? newName : dataset.getName()); request.setDescription(description != null ? description : dataset.getDescription()); - datasetsApi().updateDataset(wspId, dataset.getId(), request); + datasetsApi().updateDatasetV2(dataset.getId(), request, wspId); if (fileName != null) { - datasetsApi().uploadDataset(wspId, dataset.getId(), header, fileName.toFile()); + datasetsApi().uploadDatasetV2(dataset.getId(), wspId, header, fileName.toFile()); } return new DatasetUpdate(dataset.getName(), workspace.workspace, dataset.getId()); diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/versions/VersionsCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/versions/VersionsCmd.java index 6530e64d..b59d8583 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/versions/VersionsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/versions/VersionsCmd.java @@ -42,7 +42,7 @@ protected Response exec() throws ApiException, IOException { DatasetDto dataset = fetchDescribeDatasetResponse(parentCommand.datasetRefOptions, wspId); String datasetRef = parentCommand.datasetRefOptions.dataset.datasetName != null ? parentCommand.datasetRefOptions.dataset.datasetName : parentCommand.datasetRefOptions.dataset.datasetId; - ListDatasetVersionsResponse response = datasetsApi().listDatasetVersions(wspId, dataset.getId(), dataset.getMediaType()); + ListDatasetVersionsResponse response = datasetsApi().listDatasetVersionsV2(dataset.getId(), wspId, dataset.getMediaType()); return new DatasetVersionsList(response.getVersions(), datasetRef, parentCommand.workspace.workspace); } diff --git a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java index 30679ad3..aa82241f 100644 --- a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java @@ -95,7 +95,8 @@ void testList(OutputType format, MockServerClient mock) throws JsonProcessingExc @EnumSource(OutputType.class) void testView(OutputType format, MockServerClient mock) throws JsonProcessingException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); @@ -119,13 +120,15 @@ void testView(OutputType format, MockServerClient mock) throws JsonProcessingExc @EnumSource(OutputType.class) void testVersions(OutputType format, MockServerClient mock) throws JsonProcessingException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_versions")).withContentType(MediaType.APPLICATION_JSON) ); @@ -162,13 +165,15 @@ void testVersions(OutputType format, MockServerClient mock) throws JsonProcessin @EnumSource(OutputType.class) void testDelete(OutputType format, MockServerClient mock) { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("DELETE").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK"), exactly(1) + request().withMethod("DELETE").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(204) ); @@ -185,13 +190,15 @@ void testDelete(OutputType format, MockServerClient mock) { void testUrl(OutputType format, MockServerClient mock) throws JsonProcessingException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_versions")).withContentType(MediaType.APPLICATION_JSON) ); @@ -208,13 +215,15 @@ void testUrl(OutputType format, MockServerClient mock) throws JsonProcessingExce @EnumSource(OutputType.class) void testDownload(OutputType format, MockServerClient mock) throws IOException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/versions") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_versions")).withContentType(MediaType.APPLICATION_JSON) ); @@ -240,14 +249,16 @@ void testDownload(OutputType format, MockServerClient mock) throws IOException { @EnumSource(OutputType.class) void testAdd(OutputType format, MockServerClient mock) throws IOException { mock.when( - request().withMethod("POST").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("POST").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_created_response")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( request().withMethod("POST") - .withPath("/workspaces/249664655368293/datasets/1W3BTHWgRH71OJmOPMdG7S/upload") + .withPath("/datasets/1W3BTHWgRH71OJmOPMdG7S/upload") + .withQueryStringParameter("workspaceId", "249664655368293") .withQueryStringParameter("header", "false") , exactly(1) ).respond( @@ -266,19 +277,16 @@ void testAdd(OutputType format, MockServerClient mock) throws IOException { void testAddWithOverwrite(OutputType format, MockServerClient mock) throws IOException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) - ).respond( - response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) - ); - - mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293") + .withQueryStringParameter("search", "dataset2"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("POST").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("POST").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(JsonBody.json("{\n" + " \"dataset\": {\n" + @@ -295,7 +303,8 @@ void testAddWithOverwrite(OutputType format, MockServerClient mock) throws IOExc mock.when( request().withMethod("POST") - .withPath("/workspaces/249664655368293/datasets/1W3BTHWgRH71OJmOPMdG7S/upload") + .withPath("/datasets/1W3BTHWgRH71OJmOPMdG7S/upload") + .withQueryStringParameter("workspaceId", "249664655368293") .withQueryStringParameter("header", "false") , exactly(1) ).respond( @@ -303,7 +312,8 @@ void testAddWithOverwrite(OutputType format, MockServerClient mock) throws IOExc ); mock.when( - request().withMethod("DELETE").withPath("/workspaces/249664655368293/datasets/1W2FqBiI6WoNokQTkPkEzo"), exactly(1) + request().withMethod("DELETE").withPath("/datasets/1W2FqBiI6WoNokQTkPkEzo") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(204) ); @@ -319,13 +329,15 @@ void testAddWithOverwrite(OutputType format, MockServerClient mock) throws IOExc @EnumSource(OutputType.class) void testUpdate(OutputType format, MockServerClient mock) { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("PUT").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK"), exactly(1) + request().withMethod("PUT").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(204) ); @@ -341,20 +353,23 @@ void testUpdate(OutputType format, MockServerClient mock) { @EnumSource(OutputType.class) void testUpdateWithFile(OutputType format, MockServerClient mock) throws IOException { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); mock.when( - request().withMethod("PUT").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK"), exactly(1) + request().withMethod("PUT").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(204) ); mock.when( request().withMethod("POST") - .withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/upload") + .withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/upload") + .withQueryStringParameter("workspaceId", "249664655368293") .withQueryStringParameter("header", "false") , exactly(1) ).respond( @@ -471,7 +486,9 @@ void testShowById(OutputType format, MockServerClient mock) { @EnumSource(OutputType.class) void testHideByName(OutputType format, MockServerClient mock) { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293") + .withQueryStringParameter("search", "dataset1"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); @@ -499,7 +516,9 @@ void testHideByName(OutputType format, MockServerClient mock) { @EnumSource(OutputType.class) void testShowByName(OutputType format, MockServerClient mock) { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets"), exactly(1) + request().withMethod("GET").withPath("/datasets") + .withQueryStringParameter("workspaceId", "249664655368293") + .withQueryStringParameter("search", "dataset1"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); @@ -527,7 +546,8 @@ void testShowByName(OutputType format, MockServerClient mock) { @EnumSource(OutputType.class) void testLabelsApply(OutputType format, MockServerClient mock) { mock.when( - request().withMethod("GET").withPath("/workspaces/249664655368293/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata"), exactly(1) + request().withMethod("GET").withPath("/datasets/4D9TP0w2pM0qmwqVHgrgBK/metadata") + .withQueryStringParameter("workspaceId", "249664655368293"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/dataset_metadata")).withContentType(MediaType.APPLICATION_JSON) ); From 46ee42f27d842ded9f8d5ecdad8092380f33fcfe Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Thu, 21 May 2026 20:45:48 +0100 Subject: [PATCH 3/6] fix: resolve dataset by name when dataset hidden --- .../tower/cli/commands/datasets/AbstractDatasetsCmd.java | 2 +- .../io/seqera/tower/cli/datasets/DatasetsCmdTest.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java index 6806dab2..30127c1f 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/AbstractDatasetsCmd.java @@ -36,7 +36,7 @@ public abstract class AbstractDatasetsCmd extends AbstractApiCmd { protected DatasetDto datasetByName(Long workspaceId, String datasetName) throws ApiException { - ListDatasetsResponse listDatasetsResponse = datasetsApi().listDatasetsV2(workspaceId, null, null, datasetName, null, null, null, List.of()); + ListDatasetsResponse listDatasetsResponse = datasetsApi().listDatasetsV2(workspaceId, null, null, datasetName, null, null, "all", List.of()); if (listDatasetsResponse == null || listDatasetsResponse.getDatasets() == null) { throw new DatasetNotFoundException(workspaceRef(workspaceId)); diff --git a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java index aa82241f..b495f261 100644 --- a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java @@ -279,7 +279,8 @@ void testAddWithOverwrite(OutputType format, MockServerClient mock) throws IOExc mock.when( request().withMethod("GET").withPath("/datasets") .withQueryStringParameter("workspaceId", "249664655368293") - .withQueryStringParameter("search", "dataset2"), exactly(1) + .withQueryStringParameter("search", "dataset2") + .withQueryStringParameter("visibility", "all"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); @@ -488,7 +489,8 @@ void testHideByName(OutputType format, MockServerClient mock) { mock.when( request().withMethod("GET").withPath("/datasets") .withQueryStringParameter("workspaceId", "249664655368293") - .withQueryStringParameter("search", "dataset1"), exactly(1) + .withQueryStringParameter("search", "dataset1") + .withQueryStringParameter("visibility", "all"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); @@ -518,7 +520,8 @@ void testShowByName(OutputType format, MockServerClient mock) { mock.when( request().withMethod("GET").withPath("/datasets") .withQueryStringParameter("workspaceId", "249664655368293") - .withQueryStringParameter("search", "dataset1"), exactly(1) + .withQueryStringParameter("search", "dataset1") + .withQueryStringParameter("visibility", "all"), exactly(1) ).respond( response().withStatusCode(200).withBody(loadResource("datasets/datasets_list")).withContentType(MediaType.APPLICATION_JSON) ); From 683ca12b2681e6a16616fc06bfc16ef651db529f Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Fri, 22 May 2026 09:55:39 +0100 Subject: [PATCH 4/6] chore: minor doc update --- .../java/io/seqera/tower/cli/commands/datasets/ListCmd.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java index 60ac7403..dc9178c9 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java @@ -40,7 +40,8 @@ public class ListCmd extends AbstractDatasetsCmd { @CommandLine.Mixin public WorkspaceRequiredOptions workspace; - @CommandLine.Option(names = {"-f", "--filter"}, description = "Search expression passed to the server. Matches dataset name and other server-supported keys.") + @CommandLine.Option(names = {"-f", "--filter"}, description = "\"Optional filter criteria, allowing free text search on name or ID \" +\n" + + " \"and keywords: `username`, `label`, `visibility`, `createdAfter`, `createdBefore`, `usedAfter`, `usedBefore`. Example keyword usage: -f label:custom-label.\".") public String filter; @CommandLine.Option(names = {"--show-hidden"}, description = "Include datasets marked as hidden in the results.", defaultValue = "false") From d41f9056b8e5c4eaa3e2037ca27a479263227f0e Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Fri, 22 May 2026 13:17:54 +0100 Subject: [PATCH 5/6] fix: update reflect-config with new objects --- conf/reflect-config.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/conf/reflect-config.json b/conf/reflect-config.json index e2887db9..96122234 100644 --- a/conf/reflect-config.json +++ b/conf/reflect-config.json @@ -1200,6 +1200,18 @@ "queryAllDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.datasets.DatasetMultiRefOptions", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.datasets.DatasetMultiRefOptions$DatasetMultiRef", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.datasets.DatasetRefOptions", "allDeclaredFields":true, @@ -1224,12 +1236,30 @@ "queryAllDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.datasets.HideCmd", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.seqera.tower.cli.commands.datasets.LabelsCmd", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.datasets.ListCmd", "allDeclaredFields":true, "queryAllDeclaredMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"io.seqera.tower.cli.commands.datasets.ShowCmd", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"io.seqera.tower.cli.commands.datasets.UpdateCmd", "allDeclaredFields":true, @@ -2153,6 +2183,12 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.cli.responses.datasets.DatasetsVisibility", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"io.seqera.tower.cli.responses.labels.DeleteLabelsResponse", "allDeclaredFields":true, From 1a433c5e42ce9069a0af459ea99dbd722e018c4c Mon Sep 17 00:00:00 2001 From: "georgi.hristov" Date: Fri, 22 May 2026 13:18:10 +0100 Subject: [PATCH 6/6] fix: minor fixes --- .../seqera/tower/cli/commands/datasets/ListCmd.java | 5 +++-- .../tower/cli/responses/datasets/DatasetList.java | 13 ++++++++----- .../seqera/tower/cli/datasets/DatasetsCmdTest.java | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java index dc9178c9..7d7cbb58 100644 --- a/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/datasets/ListCmd.java @@ -40,8 +40,8 @@ public class ListCmd extends AbstractDatasetsCmd { @CommandLine.Mixin public WorkspaceRequiredOptions workspace; - @CommandLine.Option(names = {"-f", "--filter"}, description = "\"Optional filter criteria, allowing free text search on name or ID \" +\n" + - " \"and keywords: `username`, `label`, `visibility`, `createdAfter`, `createdBefore`, `usedAfter`, `usedBefore`. Example keyword usage: -f label:custom-label.\".") + @CommandLine.Option(names = {"-f", "--filter"}, description = "Optional filter criteria, allowing free text search on name or ID " + + "and keywords: `username`, `label`, `visibility`, `createdAfter`, `createdBefore`, `usedAfter`, `usedBefore`. Example keyword usage: -f label:custom-label.") public String filter; @CommandLine.Option(names = {"--show-hidden"}, description = "Include datasets marked as hidden in the results.", defaultValue = "false") @@ -74,6 +74,7 @@ protected Response exec() throws ApiException, IOException { response.getDatasets(), workspace.workspace, Boolean.TRUE.equals(showLabelsOption.showLabels), + showHidden, PaginationInfo.from(paginationOptions, response.getTotalSize()) ); } diff --git a/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java index 5d8bf002..59ae1b7b 100644 --- a/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java +++ b/src/main/java/io/seqera/tower/cli/responses/datasets/DatasetList.java @@ -33,19 +33,21 @@ public class DatasetList extends Response { public final List datasetList; public final String workspace; public final boolean showLabels; + public final boolean showHidden; @JsonIgnore @Nullable private final PaginationInfo paginationInfo; public DatasetList(List datasetList, String workspace) { - this(datasetList, workspace, false, null); + this(datasetList, workspace, false, false, null); } - public DatasetList(List datasetList, String workspace, boolean showLabels, @Nullable PaginationInfo paginationInfo) { + public DatasetList(List datasetList, String workspace, boolean showLabels, boolean showHidden, @Nullable PaginationInfo paginationInfo) { this.datasetList = datasetList; this.workspace = workspace; this.showLabels = showLabels; + this.showHidden = showHidden; this.paginationInfo = paginationInfo; } @@ -58,7 +60,8 @@ public void toString(PrintWriter out) { return; } - List desc = new ArrayList<>(List.of("ID", "Name", "Created", "Hidden")); + List desc = new ArrayList<>(List.of("ID", "Name", "Created")); + if (showHidden) desc.add("Hidden"); if (showLabels) desc.add("Labels"); TableList table = new TableList(out, desc.size(), desc.toArray(new String[0])).sortBy(0); @@ -67,9 +70,9 @@ public void toString(PrintWriter out) { List row = new ArrayList<>(List.of( ds.getId(), ds.getName(), - FormatHelper.formatDate(ds.getDateCreated()), - Boolean.TRUE.equals(ds.getHidden()) ? "yes" : "no" + FormatHelper.formatDate(ds.getDateCreated()) )); + if (showHidden) row.add(Boolean.TRUE.equals(ds.getHidden()) ? "yes" : "no"); if (showLabels) row.add(FormatHelper.formatLabels(ds.getLabels())); table.addRow(row.toArray(new String[0])); }); diff --git a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java index b495f261..3a4f364c 100644 --- a/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/datasets/DatasetsCmdTest.java @@ -434,7 +434,7 @@ void testListShowHidden(OutputType format, MockServerClient mock) throws JsonPro assertOutput(format, out, new DatasetList(Arrays.asList( parseJson("{\"id\":\"4D9TP0w2pM0qmwqVHgrgBK\",\"name\":\"dataset1\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-26T14:51:20+01:00\",\"lastUpdated\":\"2021-11-26T14:51:20+01:00\"}", DatasetDto.class), parseJson("{\"id\":\"1W2FqBiI6WoNokQTkPkEzo\",\"name\":\"dataset2\",\"description\":null,\"mediaType\":null,\"deleted\":false,\"dateCreated\":\"2021-11-29T08:05:44+01:00\",\"lastUpdated\":\"2021-11-29T08:05:44+01:00\"}", DatasetDto.class) - ), "249664655368293")); + ), "249664655368293", false, true, null)); assertEquals("", out.stdErr); assertEquals(0, out.exitCode); }