Skip to content

Commit

Permalink
feat: project add command (#857)
Browse files Browse the repository at this point in the history
  • Loading branch information
katerina20 authored Oct 25, 2024
1 parent bb43466 commit 626301b
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.crowdin.client.labels.model.AddLabelRequest;
import com.crowdin.client.labels.model.Label;
import com.crowdin.client.languages.model.Language;
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
import com.crowdin.client.projectsgroups.model.Project;
import com.crowdin.client.projectsgroups.model.ProjectSettings;
import com.crowdin.client.projectsgroups.model.Type;
Expand Down Expand Up @@ -522,4 +523,11 @@ public List<? extends Project> listProjects() {
return executeRequestFullList((limit, offset) -> this.client.getProjectsGroupsApi()
.listProjects(null, 1, limit, offset));
}

@Override
public Project addProject(AddProjectRequest request) {
return executeRequest(() -> this.client.getProjectsGroupsApi()
.addProject(request)
.getData());
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/crowdin/cli/client/ProjectClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.crowdin.client.labels.model.AddLabelRequest;
import com.crowdin.client.labels.model.Label;
import com.crowdin.client.languages.model.Language;
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
import com.crowdin.client.projectsgroups.model.Project;
import com.crowdin.client.sourcefiles.model.*;
import com.crowdin.client.sourcestrings.model.*;
Expand Down Expand Up @@ -126,4 +127,6 @@ default CrowdinProjectFull downloadFullProject() {
String getProjectUrl();

List<? extends Project> listProjects();

Project addProject(AddProjectRequest request);
}
2 changes: 2 additions & 0 deletions src/main/java/com/crowdin/cli/commands/Actions.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,6 @@ NewAction<PropertiesWithFiles, ProjectClient> preTranslate(
NewAction<ProjectProperties, ProjectClient> projectBrowse();

NewAction<ProjectProperties, ProjectClient> projectList(boolean isVerbose);

NewAction<ProjectProperties, ProjectClient> projectAdd(String name, boolean isStringBased, String sourceLanguage, List<String> languages, boolean isPublic, boolean plainView);
}
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,9 @@ public NewAction<ProjectProperties, ProjectClient> projectBrowse() {
public NewAction<ProjectProperties, ProjectClient> projectList(boolean isVerbose) {
return new ProjectListAction(isVerbose);
}

@Override
public NewAction<ProjectProperties, ProjectClient> projectAdd(String name, boolean isStringBased, String sourceLanguage, List<String> languages, boolean isPublic, boolean plainView) {
return new ProjectAddAction(name, isStringBased, sourceLanguage, languages, isPublic, plainView);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.commands.functionality.PropertiesBeanUtils;
import com.crowdin.cli.commands.functionality.RequestBuilder;
import com.crowdin.cli.properties.ProjectProperties;
import com.crowdin.cli.utils.console.ExecutionStatus;
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
import com.crowdin.client.projectsgroups.model.Project;
import com.crowdin.client.projectsgroups.model.Visibility;
import lombok.AllArgsConstructor;

import java.util.List;
import java.util.Objects;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;

@AllArgsConstructor
class ProjectAddAction implements NewAction<ProjectProperties, ProjectClient> {

private final String name;
private final boolean isStringBased;
private final String sourceLanguage;
private final List<String> languages;
private final boolean isPublic;
private final boolean plainView;

@Override
public void act(Outputter out, ProjectProperties properties, ProjectClient client) {
boolean isOrganization = PropertiesBeanUtils.isOrganization(properties.getBaseUrl());
String sourceLang = Objects.nonNull(sourceLanguage) ? sourceLanguage : "en";
Visibility visibility = isOrganization ? null : isPublic ? Visibility.OPEN : Visibility.PRIVATE;

AddProjectRequest request = RequestBuilder.addProject(name, isStringBased, sourceLang, languages, visibility, isOrganization);
Project project = client.addProject(request);
if (!plainView) {
out.println(ExecutionStatus.OK.withIcon(
String.format(RESOURCE_BUNDLE.getString("message.project.list"), project.getId(), project.getName())
));
} else {
out.println(project.getId().toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.crowdin.client.glossaries.model.GlossariesFormat;
import com.crowdin.client.glossaries.model.ImportGlossaryRequest;
import com.crowdin.client.labels.model.AddLabelRequest;
import com.crowdin.client.projectsgroups.model.*;
import com.crowdin.client.sourcefiles.model.AddBranchRequest;
import com.crowdin.client.sourcestrings.model.*;
import com.crowdin.client.stringcomments.model.AddStringCommentRequest;
Expand All @@ -22,10 +23,7 @@
import com.crowdin.client.translationmemory.model.TranslationMemoryImportRequest;
import com.crowdin.client.translations.model.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;

public class RequestBuilder {

Expand Down Expand Up @@ -325,4 +323,23 @@ private static PluralText buildPluralText(String text, String one, String two, S
Optional.ofNullable(zero).ifPresent(pluralText::setZero);
return pluralText;
}

public static AddProjectRequest addProject(String name, boolean isStringBased, String sourceLanguage, List<String> languages, Visibility visibility, boolean isOrganization) {
if (isStringBased) {
StringsBasedProjectRequest request = new StringsBasedProjectRequest();
request.setName(name);
request.setType(com.crowdin.client.projectsgroups.model.Type.STRINGS_BASED);
request.setSourceLanguageId(sourceLanguage);
request.setTargetLanguageIds(languages);
Optional.ofNullable(visibility).ifPresent(v -> request.setVisibility(v.toString()));
return request;
} else {
FilesBasedProjectRequest request = new FilesBasedProjectRequest();
request.setName(name);
request.setSourceLanguageId(sourceLanguage);
request.setTargetLanguageIds(languages);
Optional.ofNullable(visibility).ifPresent(v -> request.setVisibility(v.toString()));
return request;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.crowdin.cli.commands.picocli;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.Actions;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.properties.ProjectProperties;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

import java.util.List;

@Command(
name = CommandNames.ADD,
sortOptions = false
)
class ProjectAddSubcommand extends ActCommandProject {

@Parameters(descriptionKey = "crowdin.project.add.name")
protected String name;

@Option(names = {"-l", "--language"}, required = true, paramLabel = "...", order = -2, descriptionKey = "crowdin.project.add.language")
protected List<String> languages;

@Option(names = {"--source-language"}, paramLabel = "...", order = -2, descriptionKey = "crowdin.project.add.source-language")
protected String sourceLanguage;

@Option(names = {"--public"}, order = -2, descriptionKey = "crowdin.project.add.public")
protected boolean isPublic = false;

@Option(names = {"--string-based"}, order = -2, descriptionKey = "crowdin.project.add.string-based")
protected boolean isStringBased = false;

@Option(names = {"--plain"}, descriptionKey = "crowdin.list.usage.plain")
protected boolean plainView;

@Override
protected NewAction<ProjectProperties, ProjectClient> getAction(Actions actions) {
return actions.projectAdd(this.name, this.isStringBased, this.sourceLanguage, this.languages, this.isPublic, this.plainView);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
name = CommandNames.PROJECT,
subcommands = {
ProjectBrowseSubcommand.class,
ProjectListSubcommand.class
ProjectListSubcommand.class,
ProjectAddSubcommand.class
}
)
class ProjectSubcommand extends HelpCommand {
Expand Down
9 changes: 9 additions & 0 deletions src/main/resources/messages/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,15 @@ crowdin.project.browse.usage.customSynopsis=@|fg(green) crowdin project browse|@
crowdin.project.list.usage.description=List projects with manager access
crowdin.project.list.usage.customSynopsis=@|fg(green) crowdin project list|@ [CONFIG OPTIONS] [OPTIONS]

# CROWDIN PROJECT ADD
crowdin.project.add.usage.description=Add a new project
crowdin.project.add.usage.customSynopsis=@|fg(green) crowdin project add|@ <name> [CONFIG OPTIONS] [OPTIONS]
crowdin.project.add.name=Project name
crowdin.project.add.language=Target language identifier. Can be specified multiple times
crowdin.project.add.source-language=Defines the source language. English by default
crowdin.project.add.public=Defines whether the project is public. Private by default
crowdin.project.add.string-based=Defines whether the project is string-based

error.collect_project_info=Failed to collect project info. Please contact our support team for help
error.no_sources=No sources found for '%s' pattern. Check the source paths in your configuration file
error.only_enterprise=Operation is available only for Crowdin Enterprise
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/com/crowdin/cli/client/CrowdinProjectClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.crowdin.client.core.model.PatchRequest;
import com.crowdin.client.languages.model.LanguageResponseList;
import com.crowdin.client.languages.model.LanguageResponseObject;
import com.crowdin.client.projectsgroups.model.AddProjectRequest;
import com.crowdin.client.projectsgroups.model.Project;
import com.crowdin.client.projectsgroups.model.ProjectResponseObject;
import com.crowdin.client.projectsgroups.model.ProjectSettings;
Expand Down Expand Up @@ -120,6 +121,8 @@ public class CrowdinProjectClientTest {
String.format("%s/projects/%d/strings/%d", url, projectId, stringId);
private static final String editSourceStringUrl =
String.format("%s/projects/%d/strings/%d", url, projectId, stringId);
private static final String addProjectUrl =
String.format("%s/projects", url);

@BeforeEach
public void init() {
Expand Down Expand Up @@ -552,6 +555,20 @@ public void testEditSourceString() {
verifyNoMoreInteractions(httpClientMock);
}

@Test
public void testAddProject() {
AddProjectRequest request = new AddProjectRequest();
ProjectResponseObject response = new ProjectResponseObject() {{
setData(new Project());
}};
when(httpClientMock.post(eq(addProjectUrl), eq(request), any(), eq(ProjectResponseObject.class)))
.thenReturn(response);

client.addProject(request);

verify(httpClientMock).post(eq(addProjectUrl), eq(request), any(), eq(ProjectResponseObject.class));
verifyNoMoreInteractions(httpClientMock);
}


@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,9 @@ void testFileDelete() {
void testBranchClone() {
assertNotNull(actions.branchClone(null, null, false, false));
}

@Test
void testProjectAdd() {
assertNotNull(actions.projectAdd(null, false, null, null, false, false));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.crowdin.cli.commands.actions;

import com.crowdin.cli.client.ProjectClient;
import com.crowdin.cli.commands.NewAction;
import com.crowdin.cli.commands.Outputter;
import com.crowdin.cli.properties.ProjectProperties;
import com.crowdin.client.projectsgroups.model.*;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.mockito.Mockito.*;

class ProjectAddActionTest {

private ProjectClient client = mock(ProjectClient.class);
private NewAction<ProjectProperties, ProjectClient> action;
private ProjectProperties properties = new ProjectProperties();
private static final String PROJECT_TITLE = "project";
private static final List<String> TARGET_LANGUAGE = List.of("uk");

@Test
public void testProjectAdd_FileBased() {
properties.setBaseUrl("https://api.crowdin.com");
Project project = new Project();
project.setName(PROJECT_TITLE);
project.setId(1L);

FilesBasedProjectRequest request = new FilesBasedProjectRequest();
request.setName(PROJECT_TITLE);
request.setTargetLanguageIds(TARGET_LANGUAGE);
request.setSourceLanguageId("en");
request.setVisibility("PRIVATE");

when(client.addProject(request)).thenReturn(project);

action = new ProjectAddAction(PROJECT_TITLE, false, null, TARGET_LANGUAGE, false, false);
action.act(Outputter.getDefault(), properties, client);

verify(client).addProject(request);
verifyNoMoreInteractions(client);
}

@Test
public void testProjectAdd_StringBased() {
properties.setBaseUrl("https://api.crowdin.com");
Project project = new Project();
project.setName(PROJECT_TITLE);
project.setId(1L);

StringsBasedProjectRequest request = new StringsBasedProjectRequest();
request.setName(PROJECT_TITLE);
request.setType(Type.STRINGS_BASED);
request.setTargetLanguageIds(TARGET_LANGUAGE);
request.setSourceLanguageId("fr");
request.setVisibility("OPEN");

when(client.addProject(request)).thenReturn(project);

action = new ProjectAddAction(PROJECT_TITLE, true, "fr", TARGET_LANGUAGE, true, false);
action.act(Outputter.getDefault(), properties, client);

verify(client).addProject(request);
verifyNoMoreInteractions(client);
}

@Test
public void testProjectAdd_Enterprise() {
properties.setBaseUrl("https://companyname.crowdin.com");
Project project = new Project();
project.setName(PROJECT_TITLE);
project.setId(1L);

FilesBasedProjectRequest request = new FilesBasedProjectRequest();
request.setName(PROJECT_TITLE);
request.setTargetLanguageIds(TARGET_LANGUAGE);
request.setSourceLanguageId("fr");

when(client.addProject(request)).thenReturn(project);

action = new ProjectAddAction(PROJECT_TITLE, false, "fr", TARGET_LANGUAGE, true, true);
action.act(Outputter.getDefault(), properties, client);

verify(client).addProject(request);
verifyNoMoreInteractions(client);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ void mockActions() {
.thenReturn(actionMock);
when(actionsMock.projectBrowse()).thenReturn(actionMock);
when(actionsMock.projectList(anyBoolean())).thenReturn(actionMock);
when(actionsMock.projectAdd(any(), anyBoolean(), any(), any(), anyBoolean(), anyBoolean())).thenReturn(actionMock);
when(actionsMock.branchClone(any(), any(), anyBoolean(), anyBoolean()))
.thenReturn(actionMock);
when(actionsMock.branchMerge(any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.crowdin.cli.commands.picocli;

import org.junit.jupiter.api.Test;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.verify;

class ProjectAddSubcommandTest extends PicocliTestUtils {

@Test
public void testProjectAddInvalidOptions() {
this.executeInvalidParams(CommandNames.PROJECT, CommandNames.ADD);
}

@Test
public void testProjectAdd() {
this.execute(CommandNames.PROJECT, CommandNames.ADD, "name", "--language", "uk");
verify(actionsMock).projectAdd(any(), anyBoolean(), any(), any(), anyBoolean(), anyBoolean());
this.check(true);
}
}
16 changes: 16 additions & 0 deletions website/mantemplates/crowdin-project-add.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
:includedir: ../generated-picocli-docs
:command: crowdin-project-add

== crowdin project add

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-description]

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-synopsis]

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-arguments]

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-commands]

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-options]

include::{includedir}/{command}.adoc[tag=picocli-generated-man-section-footer]
Loading

0 comments on commit 626301b

Please sign in to comment.