Skip to content

Commit

Permalink
[1567] Add support for project templates
Browse files Browse the repository at this point in the history
Bug: #1567
Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
  • Loading branch information
AxelRICHARD committed Jan 11, 2023
1 parent 5a26218 commit 46a9bd3
Show file tree
Hide file tree
Showing 23 changed files with 1,632 additions and 34 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

- https://github.com/eclipse-sirius/sirius-components/issues/1377[#1377] [core] Switch to Java 17

=== New Features

- https://github.com/eclipse-sirius/sirius-components/issues/1567[#1567] [project] Add support for project templates

== v2023.1.0

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.graphql.datafetchers.mutation;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Objects;

import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.components.graphql.api.IExceptionWrapper;
import org.eclipse.sirius.web.services.api.projects.CreateProjectFromTemplateInput;
import org.eclipse.sirius.web.services.api.projects.IProjectService;

import graphql.schema.DataFetchingEnvironment;

/**
* The data fetcher used to create a project from a template.
*
* @author pcdavid
*/
@MutationDataFetcher(type = "Mutation", field = "createProjectFromTemplate")
public class MutationCreateProjectFromTemplateDataFetcher implements IDataFetcherWithFieldCoordinates<IPayload> {

private static final String INPUT_ARGUMENT = "input";

private final ObjectMapper objectMapper;

private final IExceptionWrapper exceptionWrapper;

private final IProjectService projectService;

public MutationCreateProjectFromTemplateDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IProjectService projectService) {
this.objectMapper = Objects.requireNonNull(objectMapper);
this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper);
this.projectService = Objects.requireNonNull(projectService);
}

@Override
public IPayload get(DataFetchingEnvironment environment) throws Exception {
Object argument = environment.getArgument(INPUT_ARGUMENT);
var input = this.objectMapper.convertValue(argument, CreateProjectFromTemplateInput.class);

return this.exceptionWrapper.wrap(() -> this.projectService.createProject(input), input);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.graphql.datafetchers.project;

import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.components.graphql.api.URLConstants;
import org.eclipse.sirius.web.services.api.projects.ProjectTemplate;

import graphql.schema.DataFetchingEnvironment;

/**
* The data fetcher used to concatenate the server image URL to the project template image path.
*
* @author pcdavid
*/
@QueryDataFetcher(type = "ProjectTemplate", field = "imageURL")
public class ProjectTemplateImageURLDataFetcher implements IDataFetcherWithFieldCoordinates<String> {

@Override
public String get(DataFetchingEnvironment environment) throws Exception {
ProjectTemplate projectTemplate = environment.getSource();
return URLConstants.IMAGE_BASE_PATH + projectTemplate.getImageURL();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.graphql.datafetchers.user;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.web.graphql.pagination.PageInfoWithCount;
import org.eclipse.sirius.web.services.api.projects.IProjectTemplateProvider;
import org.eclipse.sirius.web.services.api.projects.ProjectTemplate;

import graphql.relay.Connection;
import graphql.relay.ConnectionCursor;
import graphql.relay.DefaultConnection;
import graphql.relay.DefaultConnectionCursor;
import graphql.relay.DefaultEdge;
import graphql.relay.Edge;
import graphql.relay.PageInfo;
import graphql.relay.Relay;
import graphql.schema.DataFetchingEnvironment;

/**
* The data fetcher used to retrieve all the project templates accessible to a given viewer.
*
* @author pcdavid
*/
@QueryDataFetcher(type = "User", field = "projectTemplates")
public class UserProjectTemplatesDataFetcher implements IDataFetcherWithFieldCoordinates<Connection<ProjectTemplate>> {
private static final String PAGE_ARGUMENT = "page";

private static final String LIMIT_ARGUMENT = "limit";

private final List<IProjectTemplateProvider> projectTemplateProviders;

public UserProjectTemplatesDataFetcher(List<IProjectTemplateProvider> projectTemplateProviders) {
this.projectTemplateProviders = Objects.requireNonNull(projectTemplateProviders);
}

@Override
public Connection<ProjectTemplate> get(DataFetchingEnvironment environment) throws Exception {
int page = this.getPage(environment);
int limit = this.getLimit(environment);

// @formatter:off
List<ProjectTemplate> allProjectTemplates = this.projectTemplateProviders.stream()
.flatMap(projectTemplateProvider -> projectTemplateProvider.getProjectTemplates().stream())
.sorted(Comparator.comparing(ProjectTemplate::getLabel))
.collect(Collectors.toList());
List<Edge<ProjectTemplate>> projectTemplateEdges = allProjectTemplates.subList(page * limit, Math.min((page + 1) * limit, allProjectTemplates.size())).stream()
.map(projectTemplate -> {
String value = new Relay().toGlobalId("ProjectTemplate", projectTemplate.getId());
ConnectionCursor cursor = new DefaultConnectionCursor(value);
return new DefaultEdge<>(projectTemplate, cursor);
})
.collect(Collectors.toList());
// @formatter:on

ConnectionCursor startCursor = projectTemplateEdges.stream().findFirst().map(Edge::getCursor).orElse(null);
ConnectionCursor endCursor = null;
if (!projectTemplateEdges.isEmpty()) {
endCursor = projectTemplateEdges.get(projectTemplateEdges.size() - 1).getCursor();
}
PageInfo pageInfo = new PageInfoWithCount(startCursor, endCursor, false, false, allProjectTemplates.size());
return new DefaultConnection<>(projectTemplateEdges, pageInfo);
}

private int getPage(DataFetchingEnvironment environment) {
// @formatter:off
return Optional.<Integer> ofNullable(environment.getArgument(PAGE_ARGUMENT))
.filter(page -> page.intValue() > 0)
.orElse(0)
.intValue();
// @formatter:on
}

private int getLimit(DataFetchingEnvironment environment) {
// @formatter:off
return Optional.<Integer> ofNullable(environment.getArgument(LIMIT_ARGUMENT))
.filter(limit -> limit.intValue() > 0)
.orElse(20)
.intValue();
// @formatter:on
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,37 @@ enum Visibility {
PUBLIC
}

type ProjectTemplate {
id: ID!
label: String!
imageURL: String!
}

extend interface Viewer {
projectTemplates(page: Int, limit: Int): ViewerProjectTemplateConnection!
}

type ViewerProjectTemplateConnection {
edges: [ViewerProjectTemplateEdge!]!
pageInfo: PageInfo!
}

type ViewerProjectTemplateEdge {
node: ProjectTemplate!
}

type User implements Viewer {
id: ID!
username: String!
editingContext(editingContextId: ID!): EditingContext
project(projectId: ID!): Project
projects(page: Int): ViewerProjectConnection!
projectTemplates(page: Int, limit: Int): ViewerProjectTemplateConnection!
}

extend type Mutation {
createProject(input: CreateProjectInput!): CreateProjectPayload!
createProjectFromTemplate(input: CreateProjectFromTemplateInput!): CreateProjectFromTemplatePayload!
deleteProject(input: DeleteProjectInput!): DeleteProjectPayload!
renameProject(input: RenameProjectInput!): RenameProjectPayload!
uploadProject(input: UploadProjectInput!): UploadProjectPayload!
Expand All @@ -72,6 +93,19 @@ type CreateProjectSuccessPayload {
project: Project!
}

input CreateProjectFromTemplateInput {
id: ID!
templateId: ID!
}

union CreateProjectFromTemplatePayload = ErrorPayload | CreateProjectFromTemplateSuccessPayload

type CreateProjectFromTemplateSuccessPayload {
id: ID!
project: Project!
representationToOpen: RepresentationMetadata
}

input DeleteProjectInput {
id: ID!
projectId: ID!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.services.api.projects;

import java.util.UUID;

import org.eclipse.sirius.components.core.api.IInput;

/**
* The input object of the create project from a template mutation.
*
* @author pcdavid
*/
public record CreateProjectFromTemplateInput(UUID id, String templateId) implements IInput {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.services.api.projects;

import java.text.MessageFormat;
import java.util.Objects;
import java.util.UUID;

import org.eclipse.sirius.components.core.RepresentationMetadata;
import org.eclipse.sirius.components.core.api.IPayload;

/**
* The payload of the create project from template mutation.
*
* @author pcdavid
*/
public final class CreateProjectFromTemplateSuccessPayload implements IPayload {

private final UUID id;

private final Project project;

private final RepresentationMetadata representationToOpen;

public CreateProjectFromTemplateSuccessPayload(UUID id, Project project, RepresentationMetadata representationToOpen) {
this.id = Objects.requireNonNull(id);
this.project = Objects.requireNonNull(project);
this.representationToOpen = representationToOpen;
}

@Override
public UUID getId() {
return this.id;
}

public Project getProject() {
return this.project;
}

public RepresentationMetadata getRepresentationToOpen() {
return this.representationToOpen;
}

@Override
public String toString() {
if (this.representationToOpen != null) {
String pattern = "{0} '{'id: {1}, project: '{'id: {2}, name: {3} '}', representationToOpen: '{' id: {4}, label: {5} '}' '}'";
return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.project.getId(), this.project.getName(), this.representationToOpen.getId(),
this.representationToOpen.getLabel());
} else {
String pattern = "{0} '{'id: {1}, project: '{'id: {2}, name: {3} '}', representationToOpen: null '}'";
return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.project.getId(), this.project.getName());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019, 2022 Obeo.
* Copyright (c) 2019, 2023 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand Down Expand Up @@ -31,6 +31,8 @@ public interface IProjectService {

IPayload createProject(CreateProjectInput input);

IPayload createProject(CreateProjectFromTemplateInput input);

void delete(UUID projectId);

Optional<Project> renameProject(UUID projectId, String newName);
Expand All @@ -57,6 +59,11 @@ public IPayload createProject(CreateProjectInput input) {
return null;
}

@Override
public IPayload createProject(CreateProjectFromTemplateInput input) {
return null;
}

@Override
public void delete(UUID projectId) {
}
Expand Down
Loading

0 comments on commit 46a9bd3

Please sign in to comment.