Added basic recommendation system to attach usecases and basic helpers to
This commit is contained in:
@@ -696,9 +696,9 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini)
|
|||||||
for tutorialIndex, tutorial := range neworg.Tutorials {
|
for tutorialIndex, tutorial := range neworg.Tutorials {
|
||||||
if tutorial.Name == "Invite teammates" {
|
if tutorial.Name == "Invite teammates" {
|
||||||
neworg.Tutorials[tutorialIndex].Description = fmt.Sprintf("%d users are in your org. Org name and Image change next.", len(neworg.Users))
|
neworg.Tutorials[tutorialIndex].Description = fmt.Sprintf("%d users are in your org. Org name and Image change next.", len(neworg.Users))
|
||||||
if len(neworg.Users) > 0 {
|
if len(neworg.Users) > 1 {
|
||||||
neworg.Tutorials[tutorialIndex].Done = true
|
neworg.Tutorials[tutorialIndex].Done = true
|
||||||
neworg.Tutorials[tutorialIndex].Link = "/admin"
|
neworg.Tutorials[tutorialIndex].Link = "/admin?tab=users"
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@@ -1105,14 +1105,16 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
userOrgs = shuffle.SortOrgList(userOrgs)
|
userOrgs = shuffle.SortOrgList(userOrgs)
|
||||||
orgPriorities := org.Priorities
|
orgPriorities := org.Priorities
|
||||||
if len(org.Priorities) < 5 {
|
if len(org.Priorities) < 10 {
|
||||||
log.Printf("[WARNING] Should find and add priorities as length is less than 5 for org %s", userInfo.ActiveOrg.Id)
|
log.Printf("[WARNING] Should find and add priorities as length is less than 10 for org %s", userInfo.ActiveOrg.Id)
|
||||||
newPriorities, err := shuffle.GetPriorities(ctx, userInfo, org)
|
newPriorities, err := shuffle.GetPriorities(ctx, userInfo, org)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed getting new priorities for org %s: %s", org.Id, err)
|
log.Printf("[WARNING] Failed getting new priorities for org %s: %s", org.Id, err)
|
||||||
//orgPriorities = []shuffle.Priority{}
|
//orgPriorities = []shuffle.Priority{}
|
||||||
} else {
|
} else {
|
||||||
orgPriorities = newPriorities
|
orgPriorities = newPriorities
|
||||||
|
|
||||||
|
// A way to manage them over time
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -398,13 +398,14 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
if !foundRecommendation {
|
if !foundRecommendation {
|
||||||
// Add to start of org.Priorities
|
// Add to start of org.Priorities
|
||||||
org.Priorities = append(org.Priorities, shuffle.Priority{
|
org, _ = shuffle.AddPriority(*org, shuffle.Priority{
|
||||||
Name: fmt.Sprintf("High CPU in environment %s", orgId),
|
Name: fmt.Sprintf("High CPU in environment %s", orgId),
|
||||||
Description: fmt.Sprintf("The environment %s has been using more than %d percent CPU. This indicates you may need to look at scaling.", orgId, percentageCheck),
|
Description: fmt.Sprintf("The environment %s has been using more than %d percent CPU. This indicates you may need to look at scaling.", orgId, percentageCheck),
|
||||||
Type: "scale",
|
Type: "scale",
|
||||||
Active: true,
|
Active: true,
|
||||||
URL: fmt.Sprintf("/admin?tab=environments"),
|
URL: fmt.Sprintf("/admin?tab=environments"),
|
||||||
})
|
Severity: 1,
|
||||||
|
}, false)
|
||||||
|
|
||||||
//Make last item the first item
|
//Make last item the first item
|
||||||
org.Priorities = append([]shuffle.Priority{org.Priorities[len(org.Priorities)-1]}, org.Priorities[:len(org.Priorities)-1]...)
|
org.Priorities = append([]shuffle.Priority{org.Priorities[len(org.Priorities)-1]}, org.Priorities[:len(org.Priorities)-1]...)
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import theme from "../theme.jsx";
|
||||||
|
import {
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
Switch,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import Priority from "../components/Priority.jsx";
|
||||||
|
import { useAlert } from "react-alert";
|
||||||
|
|
||||||
|
const Priorities = (props) => {
|
||||||
|
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, checkLogin, } = props;
|
||||||
|
const [showDismissed, setShowDismissed] = React.useState(false);
|
||||||
|
const [showRead, setShowRead] = React.useState(false);
|
||||||
|
|
||||||
|
if (userdata === undefined || userdata === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{maxWidth: 1000, }}>
|
||||||
|
<h2 style={{ display: "inline" }}>Priorities</h2>
|
||||||
|
<span style={{ marginLeft: 25 }}>
|
||||||
|
Priorities identified by Shuffle to help you discover ways to protect yourself.
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="/docs/organizations#priorities"
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div style={{marginTop: 10, }}/>
|
||||||
|
<Switch
|
||||||
|
checked={showDismissed}
|
||||||
|
onChange={() => {
|
||||||
|
setShowDismissed(!showDismissed);
|
||||||
|
}}
|
||||||
|
/> Show dismissed
|
||||||
|
{userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ?
|
||||||
|
<Typography variant="h4">
|
||||||
|
No Priorities found
|
||||||
|
</Typography>
|
||||||
|
:
|
||||||
|
userdata.priorities.map((priority, index) => {
|
||||||
|
if (showDismissed === false && priority.active === false) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Priority
|
||||||
|
key={index}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
priority={priority}
|
||||||
|
checkLogin={checkLogin}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<Divider style={{marginTop: 50, marginBottom: 50, }} />
|
||||||
|
<h2 style={{ display: "inline" }}>Notifications</h2>
|
||||||
|
<span style={{ marginLeft: 25 }}>
|
||||||
|
Notifications help you find potential problems with your workflows and apps
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="/docs/organizations#notifications"
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
>
|
||||||
|
Learn more
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div/>
|
||||||
|
<Switch
|
||||||
|
checked={showRead}
|
||||||
|
onChange={() => {
|
||||||
|
setShowRead(!showRead);
|
||||||
|
}}
|
||||||
|
/> Show read
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Priorities;
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import theme from "../theme.jsx";
|
||||||
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
Button,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import { useAlert } from "react-alert";
|
||||||
|
|
||||||
|
const Priority = (props) => {
|
||||||
|
const { globalUrl, userdata, serverside, priority, checkLogin, } = props;
|
||||||
|
|
||||||
|
|
||||||
|
let navigate = useNavigate();
|
||||||
|
const changeRecommendation = (recommendation, action) => {
|
||||||
|
const data = {
|
||||||
|
action: action,
|
||||||
|
name: recommendation.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(`${globalUrl}/api/v1/recommendations/modify`, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
if (checkLogin !== undefined) {
|
||||||
|
checkLogin()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (responseJson.success === false && responseJson.reason !== undefined) {
|
||||||
|
alert.error("Failed change recommendation: ", responseJson.reason)
|
||||||
|
} else {
|
||||||
|
alert.error("Failed change recommendation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
alert.info("Failed dismissing alert. Please contact support@shuffler.io if this persists.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{border: priority.active === false ? "1px solid #000000" : priority.severity === 1 ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, marginTop: 10, marginBottom: 10, padding: 15, textAlign: "center", height: 70, textAlign: "left", backgroundColor: theme.palette.surfaceColor, display: "flex", }}>
|
||||||
|
<div style={{flex: 2, overflow: "hidden",}}>
|
||||||
|
<Typography variant="body1" >
|
||||||
|
{priority.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
{priority.description}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div style={{flex: 1, display: "flex", marginLeft: 30, }}>
|
||||||
|
<Button style={{height: 50, borderRadius: 25, marginTop: 8, width: 200, marginRight: 10, color: priority.active === false ? "white" : "black", backgroundColor: priority.active === false ? theme.palette.inputColor : "white", }} variant="contained" color="secondary" onClick={() => {navigate(priority.url)}}>
|
||||||
|
explore
|
||||||
|
</Button>
|
||||||
|
{priority.active === true ?
|
||||||
|
<Button style={{borderRadius: 25, width: 100, height: 50, marginTop: 8, }} variant="text" color="secondary" onClick={() => {
|
||||||
|
// dismiss -> get envs
|
||||||
|
changeRecommendation(priority, "dismiss")
|
||||||
|
}}>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
: null }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Priority;
|
||||||
@@ -80,6 +80,7 @@ import HandlePaymentNew from "../views/HandlePaymentNew.jsx";
|
|||||||
import OrgHeader from "../components/OrgHeader.jsx";
|
import OrgHeader from "../components/OrgHeader.jsx";
|
||||||
import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx";
|
import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx";
|
||||||
import Billing from "../components/Billing.jsx";
|
import Billing from "../components/Billing.jsx";
|
||||||
|
import Priorities from "../components/Priorities.jsx";
|
||||||
import Branding from "../components/Branding.jsx";
|
import Branding from "../components/Branding.jsx";
|
||||||
import Files from "../components/Files.jsx";
|
import Files from "../components/Files.jsx";
|
||||||
import { display, style } from "@mui/system";
|
import { display, style } from "@mui/system";
|
||||||
@@ -1359,9 +1360,9 @@ const Admin = (props) => {
|
|||||||
const admin_views = {
|
const admin_views = {
|
||||||
0: "organization",
|
0: "organization",
|
||||||
1: "cloud_sync",
|
1: "cloud_sync",
|
||||||
2: "billing",
|
2: "priorities",
|
||||||
3: "branding",
|
3: "billing",
|
||||||
4: "cache",
|
4: "branding",
|
||||||
};
|
};
|
||||||
|
|
||||||
const setConfig = (event, inputValue) => {
|
const setConfig = (event, inputValue) => {
|
||||||
@@ -2309,6 +2310,11 @@ const Admin = (props) => {
|
|||||||
Cloud Synchronization
|
Cloud Synchronization
|
||||||
</span>
|
</span>
|
||||||
/>
|
/>
|
||||||
|
<Tab
|
||||||
|
label=<span>
|
||||||
|
Priorities
|
||||||
|
</span>
|
||||||
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
disabled={!isCloud}
|
disabled={!isCloud}
|
||||||
label=<span>
|
label=<span>
|
||||||
@@ -2577,6 +2583,18 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: adminTab === 2 ?
|
: adminTab === 2 ?
|
||||||
|
<Priorities
|
||||||
|
isCloud={isCloud}
|
||||||
|
userdata={userdata}
|
||||||
|
adminTab={adminTab}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
handleGetOrg={handleGetOrg}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
setSelectedOrganization={setSelectedOrganization}
|
||||||
|
checkLogin={checkLogin}
|
||||||
|
/>
|
||||||
|
: adminTab === 3 ?
|
||||||
<Billing
|
<Billing
|
||||||
isCloud={isCloud}
|
isCloud={isCloud}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
@@ -2589,7 +2607,7 @@ const Admin = (props) => {
|
|||||||
stripeKey={props.stripeKey}
|
stripeKey={props.stripeKey}
|
||||||
handleGetOrg={handleGetOrg}
|
handleGetOrg={handleGetOrg}
|
||||||
/>
|
/>
|
||||||
: adminTab === 3 ?
|
: adminTab === 4 ?
|
||||||
<Branding
|
<Branding
|
||||||
isCloud={isCloud}
|
isCloud={isCloud}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
|
|||||||
Reference in New Issue
Block a user