Skip to content

Commit

Permalink
Merge pull request #28 from API-Flows/openapi-examples
Browse files Browse the repository at this point in the history
Operation OpenAPI examples
  • Loading branch information
gcatanese authored Apr 20, 2024
2 parents 58b01de + 13f4c40 commit 5401498
Show file tree
Hide file tree
Showing 9 changed files with 562 additions and 11 deletions.
86 changes: 83 additions & 3 deletions react-app/src/home/viewer/StepDetails.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState } from "react";

import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
Expand All @@ -10,8 +10,16 @@ import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import Link from '@mui/material/Link';
import Dialog from '@mui/material/Dialog';
import DialogContent from '@mui/material/DialogContent';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';

import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';

const StepDetails = ({ step, navigateToTab, navigateToWorkflow, operationExamples }) => {

const StepDetails = ({ step, navigateToTab, navigateToWorkflow }) => {
return (
<>
<Divider/>
Expand All @@ -23,7 +31,7 @@ const StepDetails = ({ step, navigateToTab, navigateToWorkflow }) => {
</Grid>
<Grid item xs={10}>
<Typography variant="body1" align="left">
{step.stepId}
{step.stepId}&nbsp;&nbsp;&nbsp;{operationExamples && Object.entries(operationExamples).length > 0 && <ShowExamples operationId={step.operationId} operationExamples={operationExamples} />}
</Typography>
</Grid>
{step.operationId !== null && (
Expand Down Expand Up @@ -105,6 +113,78 @@ const StepDetails = ({ step, navigateToTab, navigateToWorkflow }) => {
);
}

const ShowExamples = ({ operationId, operationExamples }) => {

const [open, setOpen] = React.useState(false);
const [selectedValue, setSelectedValue] = React.useState();

const handleClickOpenDialog = () => {
// reset selectedValue to first example
setSelectedValue(operationExamples[operationId][0].name);
setOpen(true);
};

const handleClose = () => {
setOpen(false);
};

const handleSelectChange = (event) => {
setSelectedValue(event.target.value);
};

const getJsonExample = () => {
const selectedExample = operationExamples[operationId].find(example => example.name === selectedValue);
return selectedExample ? selectedExample.example : "";
};

return (
<>
<Link onClick={() => handleClickOpenDialog()} sx = {{ cursor: 'pointer' }}>
(Examples)
</Link>

<Dialog
maxWidth="lg"
open={open}
onClose={handleClose}
PaperProps={{ style: { width: '80%', maxWidth: '800px', position: 'absolute', top: '10%', maxHeight: '500px' } }}>

<IconButton
aria-label="close"
onClick={handleClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
<DialogContent dividers="true">
<Grid container justifyContent="center">
<Grid item xs={6}>
<Select
displayEmpty
value={selectedValue}
onChange={handleSelectChange}
fullWidth
scroll="paper"
>
{operationExamples[operationId].map((example, index) => (
<MenuItem value={example.name}>{example.name}</MenuItem>
))};
</Select>
</Grid>
</Grid>
<pre style={{ fontSize: "small" }}>{getJsonExample()}</pre>
</DialogContent>
</Dialog>
</>
);

}

const ListParameters = ({ parameters, navigateToTab }) => {

const [selectedParameter, setSelectedParameter] = useState(null);
Expand Down
3 changes: 2 additions & 1 deletion react-app/src/home/viewer/Viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ const VerticalTabs = ({ workflowsSpecificationView }) => {
} {...a11yProps(2)} />
</Tabs>
<TabPanel value={value} index={0} >
<WorkflowsTabViewer workflowsSpec={workflowsSpecificationView.openAPIWorkflowParserResult.openAPIWorkflow} navigateToTab={navigateToTab} />
<WorkflowsTabViewer workflowsSpec={workflowsSpecificationView.openAPIWorkflowParserResult.openAPIWorkflow} navigateToTab={navigateToTab}
operationExamples={workflowsSpecificationView.operationExamples} />
</TabPanel>
<TabPanel value={value} index={1}>
<SourceDescriptionsTabViewer workflowsSpecificationView={workflowsSpecificationView} />
Expand Down
9 changes: 5 additions & 4 deletions react-app/src/home/viewer/WorkflowsTabViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import StepDetails from "./StepDetails.js";
import InputDetails from "./InputDetails.js";
import OutputDetails from "./OutputDetails.js";

const WorkflowsTabViewer = ({ workflowsSpec, navigateToTab }) => {
const WorkflowsTabViewer = ({ workflowsSpec, navigateToTab, operationExamples }) => {

const [selectedWorkflow, setSelectedWorkflow] = useState(workflowsSpec.workflows[0]);

Expand Down Expand Up @@ -63,13 +63,14 @@ const WorkflowsTabViewer = ({ workflowsSpec, navigateToTab }) => {
</Box>
<br/>

{selectedWorkflow && <WorkflowsViewer workflow={selectedWorkflow} navigateToTab={navigateToTab} navigateToWorkflow={navigateToWorkflow} />}
{selectedWorkflow && <WorkflowsViewer workflow={selectedWorkflow} navigateToTab={navigateToTab} navigateToWorkflow={navigateToWorkflow}
operationExamples={operationExamples} />}

</>
);
}

const WorkflowsViewer = ({ workflow, navigateToTab, navigateToWorkflow }) => {
const WorkflowsViewer = ({ workflow, navigateToTab, navigateToWorkflow, operationExamples }) => {

const [selectedCard, setSelectedCard] = useState(null);

Expand Down Expand Up @@ -155,7 +156,7 @@ const WorkflowsViewer = ({ workflow, navigateToTab, navigateToWorkflow }) => {
<Container maxWidth="xl">
{isInput(selectedCard) && (<InputDetails inputs={workflow.inputs} navigateToTab={navigateToTab} />) }
{isOutput(selectedCard) && (<OutputDetails outputs={workflow.outputs}/>) }
{isStep(selectedCard) && workflow.steps[selectedCard] && (<StepDetails step={workflow.steps[selectedCard]} navigateToTab={navigateToTab} navigateToWorkflow={navigateToWorkflow} />) }
{isStep(selectedCard) && workflow.steps[selectedCard] && (<StepDetails step={workflow.steps[selectedCard]} navigateToTab={navigateToTab} navigateToWorkflow={navigateToWorkflow} operationExamples={operationExamples} />) }
</Container>
</>
);
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/com/apiflows/model/OperationExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.apiflows.model;

public class OperationExample {

public OperationExample() {
}

public OperationExample(String name, String example) {
this.name = name;
this.example = example;
}

private String name;
private String example;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getExample() {
return example;
}

public void setExample(String example) {
this.example = example;
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/apiflows/model/WorkflowsSpecificationView.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package com.apiflows.model;

import com.apiflows.parser.OpenAPIWorkflowParserResult;
import io.swagger.v3.oas.models.examples.Example;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WorkflowsSpecificationView {

private OpenAPIWorkflowParserResult openAPIWorkflowParserResult = null;
private String componentsAsString = null;
private Map<String, List<OperationExample>> operationExamples = new HashMap<>();

public WorkflowsSpecificationView() {
}
Expand All @@ -29,4 +35,12 @@ public String getComponentsAsString() {
public void setComponentsAsString(String componentsAsString) {
this.componentsAsString = componentsAsString;
}

public Map<String, List<OperationExample>> getOperationExamples() {
return operationExamples;
}

public void setOperationExamples(Map<String, List<OperationExample>> operationExamples) {
this.operationExamples = operationExamples;
}
}
125 changes: 125 additions & 0 deletions src/main/java/com/apiflows/service/ExampleJsonHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.apiflows.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.examples.Example;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

public class ExampleJsonHelper {

private final Logger log = LoggerFactory.getLogger(WorkflowService.class);

public static final String JSON_ESCAPE_DOUBLE_QUOTE = "\"";
public static final String JSON_ESCAPE_NEW_LINE = "\n";

String getJsonFromExample(Example example) {
String ret = "";

if (example == null) {
return ret;
}

if (example.getValue() instanceof ObjectNode) {
ret = convertToJson((ObjectNode) example.getValue());
} else if (example.getValue() instanceof LinkedHashMap) {
ret = convertToJson((LinkedHashMap) example.getValue());
}

return ret;
}

public String formatJson(String json) {

ObjectMapper objectMapper = new ObjectMapper();

try {
// convert to JSON object and prettify
JsonNode actualObj = objectMapper.readTree(json);
json = Json.pretty(actualObj);

} catch (JsonProcessingException e) {
log.warn("Error formatting JSON", e);
json = "";
}

return json;
}

// array of attributes from JSON payload (ignore commas within quotes)
String[] getAttributes(String json) {
return json.split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1);
}

String convertToJson(ObjectNode objectNode) {
return formatJson(objectNode.toString());
}

// convert to JSON (string) escaping and formatting
String convertToJson(LinkedHashMap<String, Object> linkedHashMap) {
String ret = "";

return traverseMap(linkedHashMap, ret);
}

// traverse recursively
private String traverseMap(LinkedHashMap<String, Object> linkedHashMap, String ret) {

ret = ret + "{" + JSON_ESCAPE_NEW_LINE + " ";

int numVars = linkedHashMap.entrySet().size();
int counter = 1;

for (Map.Entry<String, Object> mapElement : linkedHashMap.entrySet()) {
String key = mapElement.getKey();
Object value = mapElement.getValue();

if (value instanceof String) {
ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " + JSON_ESCAPE_DOUBLE_QUOTE + value + JSON_ESCAPE_DOUBLE_QUOTE;
} else if (value instanceof Boolean) {
ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " + value;
} else if (value instanceof Integer) {
ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " + value;
} else if (value instanceof LinkedHashMap) {
String in = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": ";
ret = traverseMap(((LinkedHashMap<String, Object>) value), in);
} else if (value instanceof ArrayList<?>) {
ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + key + JSON_ESCAPE_DOUBLE_QUOTE + ": " + getJsonArray((ArrayList<Object>) value);
} else {
log.warn("Value type unrecognised: " + value.getClass());
}

if (counter < numVars) {
// add comma unless last attribute
ret = ret + "," + JSON_ESCAPE_NEW_LINE + " ";
}
counter++;
}

ret = ret + JSON_ESCAPE_NEW_LINE + "}";

return ret;
}

String getJsonArray(ArrayList<Object> list) {
String ret = "";

for(Object element: list) {
ret = ret + JSON_ESCAPE_DOUBLE_QUOTE + element + JSON_ESCAPE_DOUBLE_QUOTE + ", ";
}

if(!ret.isEmpty()) {
ret = ret.substring(0, ret.length() - 2);
}

return "[" + ret + "]";
}

}
Loading

0 comments on commit 5401498

Please sign in to comment.