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
: null}
{clickedFromOrgTab? (
Search from
Search until
):(
} /> } />
)}
{appRuns === undefined ? null : } {workflowRuns === undefined ? null : } {subflowRuns === undefined ? null : } {/*appRunCosts === undefined ? null : */}
{resultLoading ?
Loading usage for selected period (may take a while)
: { //setRowsPerPage(newPageSize) //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) }} // event for when clicking next page // Hide page changer onPageChange={(params) => { console.log("page params: ", params) }} onSelectionModelChange={(newSelection) => { console.log("newSelection: ", newSelection) //console.log("newSelection: ", newSelection) //setSelectedWorkflowExecutionsIndexes(newSelection) //var found = [] //for (var i = 0; i < newSelection.length; i++) { // // Find the workflow in the resultRows // var selected = resultRows.find((workflow) => { // return workflow.id === newSelection[i] // }) // if (selected === undefined || selected === null) { // continue // } // found.push(selected) //} //setSelectedWorkflowExecutions(found) }} // Track which items are selected /> }
) const dataWrapper = (
{data}
); return dataWrapper; } export default AppStats;