Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PLAT-1152 / add data studios 'list' command #475

Merged
merged 16 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ build
**/build-info.properties

# Location for unshared files
.user/
.user/


*.iml
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'ch.qos.logback:logback-core:1.2.11'
implementation 'ch.qos.logback:logback-classic:1.2.11'
implementation 'io.seqera.tower:tower-java-sdk:1.9.7'
implementation 'io.seqera.tower:tower-java-sdk:1.9.9'
implementation 'info.picocli:picocli:4.6.3'
implementation 'org.apache.commons:commons-compress:1.22'
implementation 'org.tukaani:xz:1.9'
Expand Down
166 changes: 160 additions & 6 deletions conf/reflect-config.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/main/java/io/seqera/tower/cli/Tower.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.seqera.tower.cli.commands.ComputeEnvsCmd;
import io.seqera.tower.cli.commands.CredentialsCmd;
import io.seqera.tower.cli.commands.DataLinksCmd;
import io.seqera.tower.cli.commands.DataStudiosCmd;
import io.seqera.tower.cli.commands.DatasetsCmd;
import io.seqera.tower.cli.commands.InfoCmd;
import io.seqera.tower.cli.commands.LaunchCmd;
Expand Down Expand Up @@ -60,6 +61,7 @@
ComputeEnvsCmd.class,
CredentialsCmd.class,
DataLinksCmd.class,
DataStudiosCmd.class,
DatasetsCmd.class,
GenerateCompletion.class,
InfoCmd.class,
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/DataStudiosCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2021-2023, 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;

import io.seqera.tower.cli.commands.datastudios.ListCmd;
import io.seqera.tower.cli.commands.datastudios.ViewCmd;
import picocli.CommandLine;

@CommandLine.Command(
name = "studios", // alternative "data-studio"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule is to follow the API, as we use studios in API this is correct, no need for data-studios

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw. you can remove that comment then if everyone agrees

description = "Manage data studios.",
subcommands = {
ViewCmd.class,
ListCmd.class,
}
)
public class DataStudiosCmd extends AbstractRootCmd {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2021-2023, 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.datastudios;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.AbstractApiCmd;
import io.seqera.tower.model.DataStudioDto;

public class AbstractStudiosCmd extends AbstractApiCmd {

protected DataStudioDto fetchDataStudio(DataStudioRefOptions dataStudioRefOptions, Long wspId) throws ApiException {
return api().describeDataStudio(dataStudioRefOptions.dataStudio.sessionId, wspId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2021-2023, 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.datastudios;

import picocli.CommandLine;

public class DataStudioRefOptions {

@CommandLine.ArgGroup(multiplicity = "1")
public DataStudioRef dataStudio;

public static class DataStudioRef {
@CommandLine.Option(names = {"-i", "--id"}, description = "DataStudio session ID.")
public String sessionId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2021-2023, 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.datastudios;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.global.PaginationOptions;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.exceptions.WorkspaceNotFoundException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.datastudios.DataStudiosList;
import io.seqera.tower.cli.utils.PaginationInfo;
import io.seqera.tower.model.DataStudioListResponse;
import picocli.CommandLine;
import picocli.CommandLine.Command;

import java.io.IOException;

@Command(
name = "list",
description = "List workspace data studios."
)
public class ListCmd extends AbstractStudiosCmd {

@CommandLine.Mixin
public WorkspaceOptionalOptions workspace;

@CommandLine.Option(names = {"-f", "--filter"}, description = "Optional filter criteria, allowing free text search on name and templateUrl " +
"and keywords: `userName`, `computeEnvName` and `status`. Example keyword usage: -f status:RUNNING")
public String filter;

@CommandLine.Mixin
PaginationOptions paginationOptions;

@Override
protected Response exec() throws ApiException, IOException {
Long wspId = workspaceId(workspace.workspace);
Integer max = PaginationOptions.getMax(paginationOptions);
Integer offset = PaginationOptions.getOffset(paginationOptions, max);

DataStudioListResponse response = new DataStudioListResponse();

try {
response = api().listDataStudios(wspId, filter, max, offset);
} catch (ApiException e) {
if (e.getCode() == 404){
throw new WorkspaceNotFoundException(wspId);
}
if (e.getCode() == 403) {
throw new TowerException(String.format("User not entitled to %s workspace", wspId));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You missed throw e; here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - thanks!

}

return new DataStudiosList(workspaceRef(wspId), response.getStudios(), PaginationInfo.from(paginationOptions, response.getTotalSize()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2021-2023, 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.datastudios;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.exceptions.DataStudioNotFoundException;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.datastudios.DataStudiosView;
import io.seqera.tower.model.DataStudioDto;
import picocli.CommandLine;

@CommandLine.Command(
name = "view",
description = "View data studio."
)
public class ViewCmd extends AbstractStudiosCmd {

@CommandLine.Mixin
public WorkspaceOptionalOptions workspace;

@CommandLine.Mixin
public DataStudioRefOptions dataStudioRefOptions;

@Override
protected Response exec() throws ApiException {
Long wspId = workspaceId(workspace.workspace);

try {
DataStudioDto dataStudioDto = fetchDataStudio(dataStudioRefOptions, wspId);

return new DataStudiosView(dataStudioDto, workspaceRef(wspId));
} catch (ApiException e) {
if (e.getCode() == 404) {
throw new DataStudioNotFoundException(dataStudioRefOptions.dataStudio.sessionId, wspId);
}
if (e.getCode() == 403) {
throw new TowerException(String.format("User not entitled to view studio '%s' at %s workspace", dataStudioRefOptions.dataStudio.sessionId, wspId));
}
throw e;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2021-2023, 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.exceptions;

public class DataStudioNotFoundException extends TowerException {

public DataStudioNotFoundException( String sessionId, Long workspaceId) {
super(String.format("DataStudio '%s' not found at workspace '%s'", sessionId, workspaceId));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021-2023, 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.datastudios;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.utils.PaginationInfo;
import io.seqera.tower.cli.utils.TableList;
import io.seqera.tower.model.DataStudioDto;
import io.seqera.tower.model.DataStudioStatusInfo;
import io.seqera.tower.model.StudioUser;

import static io.seqera.tower.cli.utils.FormatHelper.formatDataStudioStatus;

public class DataStudiosList extends Response {

public final String workspaceRef;
public final List<DataStudioDto> studios;

@JsonIgnore
@Nullable
private final PaginationInfo paginationInfo;

public DataStudiosList(String workspaceRef, List<DataStudioDto> studios, @Nullable PaginationInfo paginationInfo) {
this.workspaceRef = workspaceRef;
this.studios = studios;
this.paginationInfo = paginationInfo;
}

@Override
public void toString(PrintWriter out) {

out.println(ansi(String.format("%n @|bold Data Studios at %s workspace:|@%n", workspaceRef)));

if (studios.isEmpty()) {
out.println(ansi(" @|yellow No data studios found|@"));
return;
}

List<String> descriptions = new ArrayList<>(List.of("ID", "Name", "Description", "User", "Status"));

TableList table = new TableList(out, descriptions.size(), descriptions.toArray(new String[descriptions.size()])).sortBy(0);
table.setPrefix(" ");

studios.forEach(studio -> {

DataStudioStatusInfo statusInfo = studio.getStatusInfo();
StudioUser user = studio.getUser();
List<String> rows = new ArrayList<>(List.of(
studio.getSessionId() == null ? "" : studio.getSessionId(),
studio.getName() == null ? "" : studio.getName(),
studio.getDescription() == null ? "" : studio.getDescription(),
user == null ? "" : user.getUserName(),
formatDataStudioStatus(statusInfo == null ? null : statusInfo.getStatus())
));

table.addRow(rows.toArray(new String[rows.size()]));
});

table.print();

PaginationInfo.addFooter(out, paginationInfo);

out.println("");
}

}
Loading
Loading