Skip to content

Commit

Permalink
allow custom form
Browse files Browse the repository at this point in the history
  • Loading branch information
brig committed Sep 11, 2023
1 parent 76bc818 commit 51cd19d
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 27 deletions.
1 change: 1 addition & 0 deletions console2/src/api/service/console/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface ProcessCardEntry {
name: string;
description?: string;
icon?: string;
isCustomForm: boolean;
}

export const getActivity = (
Expand Down
17 changes: 15 additions & 2 deletions console2/src/components/organisms/UserProcessActivity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
getActivity as apiGetActivity,
listProcessCards as apiListProcessCards, ProcessCardEntry
} from '../../../api/service/console/user';
import { Button, Card, CardGroup, Header, Icon, Image } from 'semantic-ui-react';
import { Button, Card, CardGroup, Embed, Header, Icon, Image, Modal } from 'semantic-ui-react';
import { ProcessList } from '../../molecules/index';
import { ProcessEntry } from '../../../api/process';
import {
Expand Down Expand Up @@ -64,7 +64,20 @@ const renderCard = (card: ProcessCardEntry) => {

<Card.Content extra>
<div className='ui two buttons'>
<Button basic color='green' href={`/api/v1/org/${card.orgName}/project/${card.projectName}/repo/${card.repoName}/start/${card.entryPoint}`} target="_blank" rel="noopener noreferrer">Start process</Button>
{card.isCustomForm &&
<Modal trigger={<Button basic color='green'>Start process</Button>}>
<Modal.Content>
<Embed
url={`/api/v2/service/console/user/process-card/${card.id}/form`}
active={true}
/>
</Modal.Content>
</Modal>
}

{!card.isCustomForm &&
<Button basic color='green' href={`/api/v1/org/${card.orgName}/project/${card.projectName}/repo/${card.repoName}/start/${card.entryPoint}`} target="_blank" rel="noopener noreferrer">Start process</Button>
}
</div>
</Card.Content>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,14 @@
onDelete="CASCADE"/>
</changeSet>

<changeSet id="2001001" author="[email protected]">
<addColumn tableName="UI_PROCESS_CARDS">
<column name="FORM" type="bytea">
<constraints nullable="true"/>
</column>
<column name="DATA" type="jsonb">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ public interface ProcessCardEntry extends Serializable {
@Nullable
String icon();

boolean isCustomForm();

static ImmutableProcessCardEntry.Builder builder() {
return ImmutableProcessCardEntry.builder();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
* =====
*/

import com.walmartlabs.concord.common.IOUtils;
import com.walmartlabs.concord.db.AbstractDao;
import com.walmartlabs.concord.db.MainDB;
import com.walmartlabs.concord.server.ConcordObjectMapper;
import com.walmartlabs.concord.server.process.ProcessEntry;
import com.walmartlabs.concord.server.process.queue.ProcessFilter;
import com.walmartlabs.concord.server.process.queue.ProcessQueueDao;
import com.walmartlabs.concord.server.sdk.ConcordApplicationException;
import com.walmartlabs.concord.server.sdk.metrics.WithTimer;
import com.walmartlabs.concord.server.security.UserPrincipal;
import org.jooq.*;
Expand All @@ -33,29 +36,43 @@
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.function.Function;

import static com.walmartlabs.concord.server.jooq.Tables.*;
import static com.walmartlabs.concord.server.jooq.tables.Organizations.ORGANIZATIONS;
import static com.walmartlabs.concord.server.jooq.tables.Projects.PROJECTS;
import static org.jooq.impl.DSL.*;

@Path("/api/v2/service/console/user")
@javax.ws.rs.Path("/api/v2/service/console/user")
public class UserActivityResourceV2 implements Resource {

private static final String DATA_FILE_TEMPLATE = "data = %s;";

private final ProcessQueueDao processDao;
private final UserActivityDao dao;
private final ConcordObjectMapper objectMapper;

@Inject
public UserActivityResourceV2(ProcessQueueDao processDao,
UserActivityDao dao) {
UserActivityDao dao,
ConcordObjectMapper objectMapper) {
this.processDao = processDao;
this.dao = dao;
this.objectMapper = objectMapper;
}

@GET
@Path("/activity")
@javax.ws.rs.Path("/activity")
@Produces(MediaType.APPLICATION_JSON)
@WithTimer
public UserActivityResponse activity(@QueryParam("maxOwnProcesses") @DefaultValue("5") int maxOwnProcesses) {
Expand All @@ -73,7 +90,7 @@ public UserActivityResponse activity(@QueryParam("maxOwnProcesses") @DefaultValu
}

@GET
@Path("/process-card")
@javax.ws.rs.Path("/process-card")
@Produces(MediaType.APPLICATION_JSON)
@WithTimer
public List<ProcessCardEntry> processCardsList() {
Expand All @@ -83,17 +100,131 @@ public List<ProcessCardEntry> processCardsList() {
return dao.listCards(user.getId());
}

@GET
@javax.ws.rs.Path("/process-card/{cardId}/form")
@Produces(MediaType.TEXT_HTML)
@WithTimer
public Response processForm(@PathParam("cardId") UUID cardId) {

Optional<Path> o = dao.getForm(cardId, src -> {
try {
Path tmp = IOUtils.createTempFile("process-form", ".html");
Files.copy(src, tmp, StandardCopyOption.REPLACE_EXISTING);
return Optional.of(tmp);
} catch (IOException e) {
throw new ConcordApplicationException("Error while downloading custom process start form: " + cardId, e);
}
});

if (!o.isPresent()) {
return Response.status(Response.Status.NOT_FOUND).build();
}

return toBinaryResponse(o.get());
}

@GET
@javax.ws.rs.Path("/process-card/{cardId}/data.js")
@Produces("text/javascript")
@WithTimer
public Response processFormData(@PathParam("cardId") UUID cardId) {
ProcessCardEntry card = dao.get(cardId);
if (card ==null) {
return Response.status(Response.Status.NOT_FOUND).build();
}

Map<String, Object> customData = dao.getFormData(cardId);

Map<String, Object> resultData = new HashMap<>(customData != null ? customData : Collections.emptyMap());
resultData.put("org", card.orgName());
resultData.put("project", card.projectName());
resultData.put("repo", card.repoName());
resultData.put("entryPoint", card.entryPoint());

return Response.ok(formatData(resultData))
.build();
}

private String formatData(Map<String, Object> data) {
return String.format(DATA_FILE_TEMPLATE, objectMapper.toString(data));
}

private static Response toBinaryResponse(Path file) {
return Response.ok((StreamingOutput) out -> {
try (InputStream in = Files.newInputStream(file)) {
IOUtils.copy(in, out);
} finally {
Files.delete(file);
}
}).build();
}

public static class UserActivityDao extends AbstractDao {

private final ConcordObjectMapper objectMapper;

@Inject
protected UserActivityDao(@MainDB Configuration cfg) {
protected UserActivityDao(@MainDB Configuration cfg, ConcordObjectMapper objectMapper) {
super(cfg);
this.objectMapper = objectMapper;
}

public ProcessCardEntry get(UUID cardId) {
return txResult(tx -> get(tx, cardId));
}

public ProcessCardEntry get(DSLContext tx, UUID cardId) {
return buildSelect(tx)
.where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(cardId))
.fetchOne(this::toEntry);
}

public List<ProcessCardEntry> listCards(UUID userId) {
return txResult(tx -> listCards(tx, userId));
}

public Map<String, Object> getFormData(UUID cardId) {
return txResult(tx -> getFormData(tx, cardId));
}

public Map<String, Object> getFormData(DSLContext tx, UUID cardId) {
return tx.select(UI_PROCESS_CARDS.DATA)
.from(UI_PROCESS_CARDS)
.where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(cardId))
.fetchOne(r -> objectMapper.fromJSONB(r.get(UI_PROCESS_CARDS.DATA)));
}

public <T> Optional<T> getForm(UUID cardId, Function<InputStream, Optional<T>> converter) {
return txResult(tx -> getForm(tx, cardId, converter));
}

public <T> Optional<T> getForm(DSLContext tx, UUID cardId, Function<InputStream, Optional<T>> converter) {
String sql = tx.select(UI_PROCESS_CARDS.FORM)
.from(UI_PROCESS_CARDS)
.where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq((UUID) null)
.and(UI_PROCESS_CARDS.FORM.isNotNull()))
.getSQL();

return getInputStream(tx, sql, cardId, converter);
}

private static <T> Optional<T> getInputStream(DSLContext tx, String sql, UUID cardId, Function<InputStream, Optional<T>> converter) {
return tx.connectionResult(conn -> {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setObject(1, cardId);

try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
return Optional.empty();
}
try (InputStream in = rs.getBinaryStream(1)) {
return converter.apply(in);
}
}
}
});
}

public List<ProcessCardEntry> listCards(DSLContext tx, UUID userId) {
// TODO: V_USER_TEAMS
SelectConditionStep<Record1<UUID>> userTeams = tx.select(USER_TEAMS.TEAM_ID)
Expand All @@ -108,29 +239,38 @@ public List<ProcessCardEntry> listCards(DSLContext tx, UUID userId) {
.from(TEAM_UI_PROCESS_CARDS)
.where(TEAM_UI_PROCESS_CARDS.TEAM_ID.in(userTeams));

SelectConditionStep<Record11<UUID, UUID, String, UUID, String, UUID, String, String, String, String, byte[]>> query = tx.select(
UI_PROCESS_CARDS.UI_PROCESS_CARD_ID,
PROJECTS.ORG_ID,
ORGANIZATIONS.ORG_NAME,
UI_PROCESS_CARDS.PROJECT_ID,
PROJECTS.PROJECT_NAME,
UI_PROCESS_CARDS.REPO_ID,
REPOSITORIES.REPO_NAME,
UI_PROCESS_CARDS.NAME,
UI_PROCESS_CARDS.ENTRY_POINT,
UI_PROCESS_CARDS.DESCRIPTION,
UI_PROCESS_CARDS.ICON)
.from(UI_PROCESS_CARDS)
.join(REPOSITORIES, JoinType.JOIN).on(REPOSITORIES.REPO_ID.eq(UI_PROCESS_CARDS.REPO_ID))
.join(PROJECTS, JoinType.JOIN).on(PROJECTS.PROJECT_ID.eq(UI_PROCESS_CARDS.PROJECT_ID))
.join(ORGANIZATIONS, JoinType.JOIN).on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID))
SelectConditionStep<Record12<UUID, UUID, String, UUID, String, UUID, String, String, String, String, byte[], Boolean>> query =
buildSelect(tx)
.where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byUserFilter)
.or(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byTeamFilter)));

return query.fetch(this::toEntry);
}

private ProcessCardEntry toEntry(Record11<UUID, UUID, String, UUID, String, UUID, String, String, String, String, byte[]> r) {
private static SelectOnConditionStep<Record12<UUID, UUID, String, UUID, String, UUID, String, String, String, String, byte[], Boolean>> buildSelect(DSLContext tx) {

Field<Boolean> isCustomForm = when(field(UI_PROCESS_CARDS.FORM).isNotNull(), true).otherwise(false);

return tx.select(
UI_PROCESS_CARDS.UI_PROCESS_CARD_ID,
PROJECTS.ORG_ID,
ORGANIZATIONS.ORG_NAME,
UI_PROCESS_CARDS.PROJECT_ID,
PROJECTS.PROJECT_NAME,
UI_PROCESS_CARDS.REPO_ID,
REPOSITORIES.REPO_NAME,
UI_PROCESS_CARDS.NAME,
UI_PROCESS_CARDS.ENTRY_POINT,
UI_PROCESS_CARDS.DESCRIPTION,
UI_PROCESS_CARDS.ICON,
isCustomForm.as("isCustomForm"))
.from(UI_PROCESS_CARDS)
.join(REPOSITORIES, JoinType.JOIN).on(REPOSITORIES.REPO_ID.eq(UI_PROCESS_CARDS.REPO_ID))
.join(PROJECTS, JoinType.JOIN).on(PROJECTS.PROJECT_ID.eq(UI_PROCESS_CARDS.PROJECT_ID))
.join(ORGANIZATIONS, JoinType.JOIN).on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID));
}

private ProcessCardEntry toEntry(Record12<UUID, UUID, String, UUID, String, UUID, String, String, String, String, byte[], Boolean> r) {
return ProcessCardEntry.builder()
.id(r.get(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID))
.orgName(r.get(ORGANIZATIONS.ORG_NAME))
Expand All @@ -140,6 +280,7 @@ private ProcessCardEntry toEntry(Record11<UUID, UUID, String, UUID, String, UUID
.name(r.get(UI_PROCESS_CARDS.NAME))
.description(r.get(UI_PROCESS_CARDS.DESCRIPTION))
.icon(encodeBase64(r.get(UI_PROCESS_CARDS.ICON)))
.isCustomForm(r.get("isCustomForm", Boolean.class))
.build();
}

Expand Down

0 comments on commit 51cd19d

Please sign in to comment.