import React, { useState, useEffect, useContext, memo, useMemo } from 'react';
import theme from '../theme.jsx';
import classNames from "classnames";
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { DataGrid, GridColDef, GridValueGetterParams } from '@mui/x-data-grid'
import { toast } from "react-toastify"
import {
DatePicker,
DateTimePicker,
LocalizationProvider,
} from '@mui/x-date-pickers'
import {
OpenInNew as OpenInNewIcon,
} from '@mui/icons-material';
import {
CircularProgress,
Link,
Tooltip,
TextField,
IconButton,
Button,
Typography,
Grid,
Paper,
Chip,
Checkbox,
} from "@mui/material";
import {
BarChart,
GridlineSeries,
Gridline,
} from 'reaviz';
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
import { Context } from '../context/ContextApi.jsx';
const LineChartWrapper = ({keys, inputname, height, width}) => {
const [hovered, setHovered] = useState("");
const inputdata = keys.data === undefined ? keys : keys.data
return (
{inputname}
} />
}
/>
)
}
const AppStats = (defaultprops) => {
const { globalUrl, selectedOrganization, userdata, isCloud, inputWorkflows,clickedFromOrgTab } = defaultprops;
const [keys, setKeys] = useState([])
const [searches, setSearches] = useState([]);
const [appRuns, setAppruns] = useState(undefined);
const [appRunCosts, setApprunCosts] = useState(undefined);
const [workflowRuns, setWorkflowRuns] = useState(undefined);
const [subflowRuns, setSubflowRuns] = useState(undefined);
const [endTime, setEndTime] = useState("")
const [startTime, setStartTime] = useState("")
const [statistics, setStatistics] = useState(undefined);
const [filteredStatistics, setFilteredStatistics] = useState(undefined);
const [apprunCost, setApprunCost] = useState(0)
const [monthToDateCost, setMonthToDateCost] = useState(0)
const [monthTotalCost, setMonthTotalCost] = useState(0)
const [workflows, setWorkflows] = useState(inputWorkflows === undefined ? [] : inputWorkflows)
const [resultRows, setResultRows] = useState([])
const [resultLoading, setResultLoading] = useState(true)
const includedExecutions = selectedOrganization?.sync_features?.app_executions !== undefined ? selectedOrganization?.sync_features?.app_executions?.limit : 0
useEffect(() => {
if (workflows === undefined || workflows === null || workflows.length === 0) {
getAvailableWorkflows()
}
}, [])
const getWorkflowStats = async (workflow, startTime, endTime) => {
if (!userdata.support) {
return workflow
}
if (workflow.id === undefined || workflow.id === null || workflow.id === "") {
return workflow
}
var starttime = ""
var endtime = ""
try {
starttime = startTime === undefined || startTime === null || startTime === "" ? "" : new Date(startTime).toISOString()
endtime = endTime === undefined || endTime === null || endTime == "" ? "" : new Date(endTime).toISOString()
} catch(err) {
console.log("Error converting start/end time", err)
toast("Bad start/endtime. Please try again")
return
}
var url = `${globalUrl}/api/v1/workflows/${workflow.id}/executions/count`
if (starttime !== "") {
url += `?start_time=${starttime}`
}
if (endtime !== "") {
if (starttime !== "") {
url += `&end_time=${endtime}`
} else {
url += `?end_time=${endtime}`
}
}
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
}).catch((error) => {
console.log("Error getting workflow stats: " + error);
return workflow
})
if (response.status !== 200) {
console.log("Status not 200 for workflow stats URL: " + url);
return workflow
}
const data = await response.json();
if (data === undefined || data === null) {
console.log("No data for workflow stats URL: " + url);
return workflow
}
if (data.success === false) {
console.log("No success for workflow stats URL: " + url, data.reason);
return workflow
}
workflow.runcount = data.count
return workflow
}
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
if (!userdata.support) {
return
}
if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) {
console.log("Not workflows")
return
}
// Only do latest 20
setResultLoading(true)
const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime));
const allData = Promise.all(promises);
allData.then((data) => {
var total = 0
for (var i = 0; i < data.length; i++) {
if (data[i].runcount !== undefined) {
total += data[i].runcount
} else {
data[i].runcount = 0
}
}
data[0].runcount = total
// Sort data by runcount
data.sort((a, b) => (a.runcount < b.runcount) ? 1 : -1)
setResultRows(data)
setResultLoading(false)
})
}
const getAvailableWorkflows = () => {
fetch(globalUrl + "/api/v1/workflows", {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!");
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson !== undefined) {
var foundWorkflows = [{"id": "", "name": "All Workflows",}]
foundWorkflows.push(...responseJson)
setWorkflows(foundWorkflows)
loadWorkflowStats(foundWorkflows)
}
})
.catch((error) => {
console.log("Error getting workflows: " + error);
})
}
// Cost in old contracts: 0.0009
// Old contracts also always included 150.000 executions
const invocationCost = includedExecutions === 150000 || includedExecutions === 250000 ? 0.0009 : typecost_single
const defaultAmount = 10000
useEffect(() => {
if (statistics === undefined || statistics === null) {
return
}
if (statistics["daily_statistics"] === undefined || statistics["daily_statistics"] === null) {
setFilteredStatistics(statistics)
return
}
// Calculate month to date cost
var mtd_cost = 0
for (let key in statistics["daily_statistics"]) {
const item = statistics["daily_statistics"][key]
if (item["date"] === undefined) {
continue
}
const date = new Date(item["date"])
const today = new Date()
if (date.getMonth() === today.getMonth()) {
mtd_cost += (item["app_executions"] * invocationCost)
}
}
if (isCloud && mtd_cost !== monthToDateCost) {
// Find how many days there have been in the current month
const today = new Date()
const daysInMonth = new Date(today.getFullYear(), today.getMonth()+1, 0).getDate()
// Find what day we are on
const day = today.getDate()
// Find how many days are left in the month
const daysLeft = daysInMonth - day
// Calculate the cost of the entire month
var monthTotalCost = mtd_cost/day*daysInMonth
monthTotalCost -= defaultAmount*invocationCost
monthTotalCost -= includedExecutions*invocationCost
// Remove included amount
//const defaultAmount = 10000
mtd_cost -= defaultAmount*invocationCost
mtd_cost -= includedExecutions*invocationCost
if (monthTotalCost > 0) {
setMonthTotalCost(monthTotalCost.toFixed(2))
}
if (mtd_cost > 0) {
setMonthToDateCost(mtd_cost.toFixed(2))
}
}
// Make a date at the 1st of the current month
var foundstarttime = (new Date())
foundstarttime.setDate(1)
if (startTime !== "" && startTime !== undefined && startTime !== null) {
foundstarttime = startTime
}
// Set to tomorrow by default
var foundendtime = (new Date())
foundendtime.setDate(foundendtime.getDate() + 1)
// Check if endtime is after the daily statistics["date"] string
if (endTime !== "" && endTime !== undefined && endTime !== null) {
foundendtime = endTime
}
// Check if start time is before the daily statistics["date"] string
var newlist = []
for (let key in statistics["daily_statistics"]) {
const item = statistics["daily_statistics"][key]
if (item["date"] === undefined) {
continue
}
const date = new Date(item["date"])
if (date >= foundstarttime) {
if (date <= foundendtime) {
newlist.push(item)
}
}
}
// If newlist is empty, set the timestamp to 1 year back and check if there are any statistics there
// If foundstarttime is more than 30 days back, don't do this
/*
if (newlist.length === 0 && foundstarttime.getDate() > 30) {
// Set the timestamp to be back
foundstarttime.setFullYear(foundstarttime.getFullYear() - 1)
setStartTime(foundstarttime)
console.log("IN HERE")
}
*/
var tmpstats = JSON.parse(JSON.stringify(statistics))
var workflowexecutions = 0
var appexecutions = 0
var estimatedcost = 0
if (newlist.length > 0) {
tmpstats["daily_statistics"] = newlist
for (let key in newlist) {
const item = newlist[key]
if (item["workflow_executions"] === undefined) {
continue
}
workflowexecutions += item["workflow_executions"]
appexecutions += item["app_executions"]
estimatedcost += (item["app_executions"] * invocationCost)
}
tmpstats["monthly_workflow_executions"] = workflowexecutions
tmpstats["monthly_app_executions"] = appexecutions
}
// Make estimatedcost have max 2 decimals
if (isCloud) {
// Exclude includedExecutions*month
// const includedExecutions = 150000
//estimatedcost -= (includedExecutions * invocationCost)
setApprunCost(estimatedcost.toFixed(2))
}
setFilteredStatistics(tmpstats)
handleDataSetting(tmpstats, "day")
if (workflows !== undefined && workflows !== null && workflows.length > 0) {
var foundWorkflows = [{"id": "", "name": "All Workflows",}]
var tmpworkflows = workflows.filter((workflow) => workflow.id !== undefined && workflow.id !== null && workflow.id !== "")
foundWorkflows.push(...tmpworkflows)
loadWorkflowStats(foundWorkflows, startTime, endTime)
}
}, [statistics, startTime, endTime])
const handleStartTimeChange = (date) => {
setStartTime(date)
}
const handleEndTimeChange = (date) => {
setEndTime(date)
}
const handleDataSetting = (inputdata, grouping) => {
if (inputdata === undefined || inputdata === null) {
return
}
const dailyStats = inputdata.daily_statistics
if (dailyStats === undefined || dailyStats === null) {
return
}
var appRuns = {
"key": "App Runs",
"data": []
}
var workflowRuns = {
"key": "Workflow Runs (includes subflows)",
"data": []
}
var subflowRuns = {
"key": "Subflow Runs",
"data": []
}
var appcostRuns = {
"key": "Cost of App Runs",
"data": []
}
for (let key in dailyStats) {
// Always skips first one as it has accumulated data in it
if (key === 0) {
continue
}
const item = dailyStats[key]
if (item["date"] === undefined) {
console.log("No date: ", item)
continue
}
// Check if app_executions key in item
if (item["app_executions"] !== undefined && item["app_executions"] !== null) {
appRuns["data"].push({
key: new Date(item["date"]),
data: item["app_executions"]
})
// Add number
appcostRuns["data"].push({
key: new Date(item["date"]),
data: (item["app_executions"] * invocationCost).toFixed(2)
})
}
// Check if workflow_executions key in item
if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) {
workflowRuns["data"].push({
key: new Date(item["date"]),
data: item["workflow_executions"]
})
}
if (item["subflow_executions"] !== undefined && item["subflow_executions"] !== null) {
subflowRuns["data"].push({
key: new Date(item["date"]),
data: item["subflow_executions"]
})
}
}
// Adds data for today
if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) {
appRuns["data"].push({
key: new Date(),
data: inputdata["daily_app_executions"]
})
appcostRuns["data"].push({
key: new Date(),
data: (inputdata["daily_app_executions"] * invocationCost).toFixed(2)
})
}
if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) {
workflowRuns["data"].push({
key: new Date(),
data: inputdata["daily_workflow_executions"]
})
}
if (inputdata["daily_subflow_executions"] !== undefined && inputdata["daily_subflow_executions"] !== null) {
subflowRuns["data"].push({
key: new Date(),
data: inputdata["daily_subflow_executions"]
})
}
setSubflowRuns(subflowRuns)
setWorkflowRuns(workflowRuns)
setAppruns(appRuns)
setApprunCosts(appcostRuns)
}
const getStats = (orgid) => {
if (orgid === undefined || orgid === null) {
return
}
fetch(`${globalUrl}/api/v1/orgs/${orgid}/stats`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!: ", response.status);
return;
}
return response.json();
})
.then((responseJson) => {
if (responseJson["success"] === false) {
return
}
setStatistics(responseJson)
handleDataSetting(responseJson, "day")
})
.catch((error) => {
console.log("error: ", error)
});
}
useEffect(() => {
if(selectedOrganization?.id?.length > 0) {
getStats(selectedOrganization.id)
}
}, [selectedOrganization])
const paperStyle = {
textAlign: "center",
padding: 40,
margin: 5,
backgroundColor: theme.palette.platformColor,
border: "1px solid rgba(255,255,255,0.3)",
maxWidth: 300,
}
const columns: GridColDef[] = [
{
field: 'workflow.name',
headerName: 'Workflow Name',
width: 350,
renderCell: (params) => {
return (
{
}}>
{params.row.name}
)
}
},
{
field: 'workflow.runcount',
headerName: 'Workflow Runs in selected period',
width: 250,
renderCell: (params) => {
return (
{
}}>
{params.row.runcount}
)
}
},
{
field: 'triggers',
headerName: 'Triggers',
width: 100,
renderCell: (params) => {
if (params.row.id === "") {
return null
}
const cnt = params.row.triggers === undefined || params.row.triggers === null ? 0 : params.row.triggers.length
return (
{
}}>
{cnt}
)
}
},
{
field: 'actions',
headerName: 'Actions',
width: 100,
renderCell: (params) => {
if (params.row.id === "") {
return null
}
const cnt = params.row.actions === undefined || params.row.actions === null ? 0 : params.row.actions.length
return (
{
}}>
{cnt}
)
}
},
/*{
field: 'last editor',
headerName: 'Last Editor',
width: 100,
renderCell: (params) => {
if (params.row.id === "") {
return null
}
const lastEditor = params.row.lasteditor === undefined || params.row.lasteditor === null ? "" : params.row.lasteditor
return (
{
}}>
{lastEditor}
)
}
},*/
{
field: 'explore',
headerName: 'Explore',
width: 100,
renderCell: (params) => {
if (params.row.id === "") {
return null
}
return (
)
}
},
]
const data = (
All shown statistics are gathered from Your Organisation Statistics.
It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. The billing tracker is in Beta, and is always calculated manually before being invoiced.
{filteredStatistics !== undefined ?
The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}.
}>
${selectedOrganization.lead_info.customer === false && selectedOrganization.lead_info.pov === false ?
0
:
apprunCost
}
Period Cost
App runs in the selected period
}>
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
App Runs
Workflow runs in the selected period
}>
{filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions}
Workflow Runs
Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}.
}>
${monthTotalCost}
Estimated cost