#285: Made apps send available versions and mapped them in workflow view

This commit is contained in:
frikky
2021-04-04 17:28:32 +02:00
parent 0aa5d95580
commit 07bbf19d33
6 changed files with 174 additions and 218 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ module shuffle
go 1.13
replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
require (
@@ -10,14 +11,15 @@ require (
cloud.google.com/go/datastore v1.4.0
cloud.google.com/go/pubsub v1.3.1
cloud.google.com/go/storage v1.12.0
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/basgys/goxml2json v1.1.0
github.com/frikky/kin-openapi v0.38.0
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 // indirect
github.com/frikky/kin-openapi v0.38.0
github.com/frikky/shuffle-shared v0.0.23
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0
+2
View File
@@ -48,6 +48,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
+1 -1
View File
@@ -6189,11 +6189,11 @@ func initHandlers() {
// From here down isnt checked for org specific
r.HandleFunc("/api/v1/apps/{appId}", shuffle.UpdateWorkflowAppConfig).Methods("PATCH", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}", shuffle.DeleteWorkflowApp).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/config", shuffle.GetWorkflowAppConfig).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/config", getWorkflowAppConfig).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps", getWorkflowApps).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/apps", setNewWorkflowApp).Methods("PUT", "OPTIONS")
r.HandleFunc("/api/v1/apps/search", getSpecificApps).Methods("POST", "OPTIONS")
-207
View File
@@ -3396,113 +3396,6 @@ func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(`{"success": true}`))
}
func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
ctx := context.Background()
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
app, err := shuffle.GetApp(ctx, fileId, shuffle.User{})
if err != nil {
log.Printf("[WARNING] Error getting app %s (app config): %s", fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "App doesn't exist"}`))
return
}
//if IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
// Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"`
//log.Printf("Sharing: %s", app.Sharing)
//log.Printf("Generated: %s", app.Generated)
//log.Printf("Downloaded: %s", app.Downloaded)
// FIXME - Handle sharing and such PROPERLY
if app.Sharing && app.Generated {
log.Printf("CAN SHARE APP!")
parsedApi, err := getOpenApiDatastore(ctx, fileId)
if err != nil {
log.Printf("[WARNING] OpenApi doesn't exist for: %s - err: %s", fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if len(parsedApi.ID) > 0 {
parsedApi.Success = true
} else {
parsedApi.Success = false
}
//log.Printf("PARSEDAPI: %#v", parsedApi)
data, err := json.Marshal(parsedApi)
if err != nil {
log.Printf("[WARNING] Error parsing api json: %s", err)
resp.WriteHeader(422)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed marshalling new parsed swagger: %s"}`, err)))
return
}
resp.WriteHeader(200)
resp.Write(data)
return
}
user, userErr := shuffle.HandleApiAuthentication(resp, request)
if userErr != nil {
log.Printf("[WARNING] Api authentication failed in get app: %s", userErr)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Id != app.Owner {
log.Printf("[WARNING] Wrong user (%s) for app %s", user.Username, app.Name)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
log.Printf("[INFO] Getting app %s (OpenAPI)", fileId)
parsedApi, err := getOpenApiDatastore(ctx, fileId)
if err != nil {
log.Printf("OpenApi doesn't exist for: %s - err: %s", fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
//log.Printf("Parsed API: %#v", parsedApi)
if len(parsedApi.ID) > 0 {
parsedApi.Success = true
} else {
parsedApi.Success = false
}
data, err := json.Marshal(parsedApi)
if err != nil {
resp.WriteHeader(422)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed marshalling new parsed swagger: %s"}`, err)))
return
}
resp.WriteHeader(200)
resp.Write(data)
}
func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
@@ -3525,34 +3418,6 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
//return
}
//if item, err := memcache.Get(ctx, memcacheName); err == memcache.ErrCacheMiss {
// // Not in cache
// log.Printf("Apps not in cache.")
//} else if err != nil {
// log.Printf("Error getting item: %v", err)
//} else {
// // FIXME - verify if value is ok? Can unmarshal etc.
// allApps := item.Value
// if userErr == nil && len(user.PrivateApps) > 0 {
// var parsedApps []WorkflowApp
// err = json.Unmarshal(allApps, &parsedApps)
// if err == nil {
// log.Printf("Shouldve added %d apps", len(user.PrivateApps))
// user.PrivateApps = append(user.PrivateApps, parsedApps...)
// tmpApps, err := json.Marshal(user.PrivateApps)
// if err == nil {
// allApps = tmpApps
// }
// }
// }
// resp.WriteHeader(200)
// resp.Write(allApps)
// return
//}
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 500)
if err != nil {
log.Printf("Failed getting apps (getworkflowapps): %s", err)
@@ -3560,52 +3425,8 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(`{"success": false}`))
return
}
//log.Printf("Length: %d", len(workflowapps))
// FIXME - this is really garbage, but is here to protect again null values etc.
newapps := workflowapps
/*
skipApps := []string{"Shuffle Subflow"}
newapps := []WorkflowApp{}
baseApps := []WorkflowApp{}
for _, workflowapp := range workflowapps {
//if !workflowapp.Activated && workflowapp.Generated {
// continue
//}
if workflowapp.Owner != user.Id && user.Role != "admin" && !workflowapp.Sharing {
continue
}
continueOuter := false
for _, skip := range skipApps {
if workflowapp.Name == skip {
continueOuter = true
break
}
}
if continueOuter {
continue
}
//workflowapp.Environment = "cloud"
newactions := []WorkflowAppAction{}
for _, action := range workflowapp.Actions {
//action.Environment = workflowapp.Environment
if len(action.Parameters) == 0 {
action.Parameters = []WorkflowAppActionParameter{}
}
newactions = append(newactions, action)
}
workflowapp.Actions = newactions
newapps = append(newapps, workflowapp)
baseApps = append(baseApps, workflowapp)
}
*/
if len(user.PrivateApps) > 0 {
found := false
@@ -3625,7 +3446,6 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
// Double unmarshal because of user apps
newbody, err := json.Marshal(newapps)
//newbody, err := json.Marshal(workflowapps)
if err != nil {
log.Printf("Failed unmarshalling all newapps: %s", err)
resp.WriteHeader(401)
@@ -3633,33 +3453,6 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
return
}
//basebody, err := json.Marshal(baseApps)
////newbody, err := json.Marshal(workflowapps)
//if err != nil {
// log.Printf("Failed unmarshalling all baseapps: %s", err)
// resp.WriteHeader(401)
// resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow apps"}`)))
// return
//}
// Refreshed every hour
//item := &memcache.Item{
// Key: memcacheName,
// Value: basebody,
// Expiration: time.Minute * 60,
//}
//if err := memcache.Add(ctx, item); err == memcache.ErrNotStored {
// if err := memcache.Set(ctx, item); err != nil {
// log.Printf("Error setting item: %v", err)
// }
//} else if err != nil {
// log.Printf("error adding item: %v", err)
//} else {
// log.Printf("Set cache for %s", item.Key)
//}
//log.Println(string(body))
//log.Println(string(newbody))
resp.WriteHeader(200)
resp.Write(newbody)
}
+93 -7
View File
@@ -1302,9 +1302,10 @@ const AngularWorkflow = (props) => {
}
const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version)
console.log(apps)
const curapp = apps.find(a => a.name === curaction.app_name && (a.app_version === curaction.app_version || a.loop_versions.includes(curaction.app_version)))
if (!curapp || curapp === undefined) {
alert.error("App "+curaction.app_name+" not found. Is it activated?")
alert.error(`App ${curaction.app_name}:${curaction.app_version} not found. Is it activated?`)
//return
} else {
@@ -2169,9 +2170,9 @@ const AngularWorkflow = (props) => {
<div style={variableScrollStyle}>
What are <a rel="norefferer" target="_blank" href="https://shuffler.io/docs/workflows#workflow_variables" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>WORKFLOW variables?</a>
{workflow.workflow_variables === null ?
null : workflow.workflow_variables.map(variable=> {
null : workflow.workflow_variables.map((variable, index) => {
return (
<div key={variable.name} >
<div key={index} >
<Paper square style={paperVariableStyle} onClick={() => {
}}>
<div style={{marginLeft: "10px", marginTop: "5px", marginBottom: "5px", width: "2px", backgroundColor: yellow, marginRight: "5px"}} />
@@ -2906,7 +2907,6 @@ const AngularWorkflow = (props) => {
id="appsearch"
onKeyPress={(event) => {
if (event.key === "Enter") {
console.log("ENTER!")
runSearch(event.target.value)
}
}}
@@ -4215,6 +4215,66 @@ const AngularWorkflow = (props) => {
borderRadius: borderRadius,
}
const getApp = (appId, setApp) => {
fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
headers: {
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
//alert.success("Successfully GOT app "+appId)
} else {
alert.error("Failed getting app")
}
return response.json()
})
.then((responseJson) => {
console.log(responseJson)
if (setApp && responseJson.actions !== undefined && responseJson.actions !== null) {
if (selectedApp.versions !== undefined && selectedApp.versions !== null) {
responseJson.versions = selectedApp.versions
}
if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) {
responseJson.loop_versions = selectedApp.loop_versions
}
var foundAction = responseJson.actions.find(action => action.name === selectedAction.name)
console.log("Old : ", selectedAction)
console.log("Found: ", foundAction)
if (foundAction !== null && foundAction !== undefined) {
for (var paramkey in foundAction.parameters) {
const param = foundAction.parameters[paramkey]
const foundParam = selectedAction.parameters.find(item => item.name === param.name)
if (foundParam === undefined) {
console.log("COULDNT find Param: ", param)
} else {
console.log("FoundP: ", foundParam)
foundAction.parameters[paramkey] = foundParam
}
}
} else {
alert.error("Couldn't find action "+selectedAction.name)
}
// Updating params for the new action
selectedAction.parameters = foundAction.parameters
selectedAction.app_id = appId
selectedAction.app_version = responseJson.app_version
setSelectedAction(selectedAction)
setSelectedApp(responseJson)
}
})
.catch(error => {
alert.error(error.toString())
});
}
const innerTextfieldStyle = {
color: "white",
@@ -4267,16 +4327,42 @@ const AngularWorkflow = (props) => {
</span>
</div>
</div>
<div>
<div style={{display: "flex", flexDirection: "column",}}>
{selectedAction.id === workflow.start ? null :
<Tooltip color="primary" title={"Make this node the start action"} placement="top">
<Button style={{zIndex: 5000, marginTop: "15px",}} color="primary" variant="outlined" onClick={(e) => {
<Button style={{zIndex: 5000, marginTop: 10,}} color="primary" variant="outlined" onClick={(e) => {
defineStartnode(e)
}}>
<KeyboardArrowRightIcon />
</Button>
</Tooltip>
}
{selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 0 ?
<Select
defaultValue={selectedAction.app_version}
onChange={(event) => {
const newversion = selectedApp.versions.find(tmpApp => tmpApp.version == event.target.value)
if (newversion !== undefined && newversion !== null) {
getApp(newversion.id, true)
}
}}
style={{marginTop: 10, backgroundColor: theme.palette.surfaceColor, backgroundColor: inputColor, color: "white", height: 35, marginleft: 10,}}
SelectDisplayProps={{
style: {
marginLeft: 10,
}
}}
>
{selectedApp.versions.map((data, index) => {
return (
<MenuItem key={index} style={{backgroundColor: inputColor, color: "white"}} value={data.version}>
{data.version}
</MenuItem>
)
})}
</Select>
: null }
</div>
</div>
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
+75 -2
View File
@@ -631,6 +631,37 @@ const Apps = (props) => {
</Typography>
</div>
</div>
{selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 0 ?
<Select
defaultValue={selectedApp.app_version}
onChange={(event) => {
console.log("Changing version to index "+event.target.value)
const newversion = selectedApp.versions.find(tmpApp => tmpApp.version == event.target.value)
console.log("New version: ", newversion)
selectedApp.app_version = selectedApp.app_version
setSelectedApp(selectedApp)
if (newversion !== undefined && newversion !== null) {
getApp(newversion.id, true)
}
}}
style={{position: "absolute", top: -10, right: isCloud ? 50 : 0, backgroundColor: theme.palette.surfaceColor, backgroundColor: inputColor, color: "white", height: 35, marginleft: 10,}}
SelectDisplayProps={{
style: {
marginLeft: 10,
}
}}
>
{selectedApp.versions.map((data, index) => {
return (
<MenuItem key={data.version} style={{backgroundColor: inputColor, color: "white"}} value={data.version}>
{data.version}
</MenuItem>
)
})}
</Select>
: null }
{isCloud ?
<a href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
<IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} >
@@ -835,7 +866,7 @@ const Apps = (props) => {
var tmpapps = searchableApps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
newapps.push(...tmpapps)
console.log(newapps)
//console.log(newapps)
setFilteredApps(newapps)
//if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
@@ -1108,7 +1139,7 @@ const Apps = (props) => {
setValidation(true)
var cors = "cors"
if (openApi.includes("localhost")) {
if (openApi.includes("= localhost")) {
cors = "no-cors"
}
@@ -1126,6 +1157,48 @@ const Apps = (props) => {
});
}
const getApp = (appId, setApp) => {
fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
headers: {
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
//alert.success("Successfully GOT app "+appId)
} else {
alert.error("Failed getting app")
}
return response.json()
})
.then((responseJson) => {
console.log(responseJson)
if (setApp) {
if (selectedApp.versions !== undefined && selectedApp.versions !== null) {
responseJson.versions = selectedApp.versions
}
if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) {
responseJson.loop_versions = selectedApp.loop_versions
}
//alert.info("Should set app to selected")
if (responseJson.actions !== undefined && responseJson.actions !== null && responseJson.actions.length > 0) {
setSelectedAction(responseJson.actions[0])
} else {
setSelectedAction({})
}
setSelectedApp(responseJson)
}
})
.catch(error => {
alert.error(error.toString())
});
}
const deleteApp = (appId) => {
alert.info("Attempting to delete app")
fetch(globalUrl+"/api/v1/apps/"+appId, {