import React, { useState, useEffect, useContext, useCallback } from 'react'; import {getTheme} from '../theme.jsx'; import classNames from "classnames"; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' import { DataGrid } 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, Box, } from "@mui/material"; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; import LineChartWrapper from '../components/LineChartWrapper.jsx'; import { Context } from '../context/ContextApi.jsx'; const AppStats = (defaultprops) => { const { globalUrl, selectedOrganization, userdata, isCloud, inputWorkflows, clickedFromOrgTab, syncStats, statistics, monthlyAppRunsParent, setMonthlyAppRunsParent, monthlyAllSuborgExecutions, setMonthlyAllSuborgExecutions, currentTab } = defaultprops; const [keys, setKeys] = useState([]) const [searches, setSearches] = useState([]); const [appRuns, setAppruns] = useState(undefined); const [childOrgsAppRuns, setChildOrgsAppRuns] = 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 [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 { themeMode, brandColor } = useContext(Context); const [onpremAppRuns, setOnpremAppRuns] = useState(0) const theme = getTheme(themeMode, brandColor) 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 handleDataSetting = useCallback((inputdata, grouping) => { if (inputdata === undefined || inputdata === null) { return } const statKey = syncStats === true ? "onprem_stats" : "daily_statistics" const dailyStats = inputdata[statKey] if (dailyStats === undefined || dailyStats === null) { return } var appRuns = { "key": "App Runs", "data": [] } var childorgappRuns = { "key": "Child Org 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"]).toISOString(), data: item["app_executions"] }) // Add number appcostRuns["data"].push({ key: new Date(item["date"]).toISOString(), data: (item["app_executions"] * invocationCost).toFixed(2) }) } if (item["child_app_executions"] !== undefined && item["child_app_executions"] !== null) { childorgappRuns["data"].push({ key: new Date(item["date"]).toISOString(), data: item["child_app_executions"] }) } // Check if workflow_executions key in item if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { workflowRuns["data"].push({ key: new Date(item["date"]).toISOString(), data: item["workflow_executions"] }) } if (item["subflow_executions"] !== undefined && item["subflow_executions"] !== null) { subflowRuns["data"].push({ key: new Date(item["date"]).toISOString(), data: item["subflow_executions"] }) } } // Only add today's data if endTime is not set or if today falls within the selected date range const today = new Date() const todayStartOfDay = new Date(today) todayStartOfDay.setHours(0, 0, 0, 0) const shouldAddTodayData = endTime === "" || endTime === undefined || endTime === null || (new Date(endTime) >= todayStartOfDay) if (!syncStats && shouldAddTodayData) { // Adds data for today if (inputdata["daily_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) { appRuns["data"].push({ key: new Date().toISOString(), data: inputdata["daily_app_executions"] }) appcostRuns["data"].push({ key: new Date().toISOString(), data: (inputdata["daily_app_executions"] * invocationCost).toFixed(2) }) } if (inputdata["daily_child_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) { childorgappRuns["data"].push({ key: new Date().toISOString(), data: inputdata["daily_child_app_executions"] }) } if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { workflowRuns["data"].push({ key: new Date().toISOString(), data: inputdata["daily_workflow_executions"] }) } if (inputdata["daily_subflow_executions"] !== undefined && inputdata["daily_subflow_executions"] !== null) { subflowRuns["data"].push({ key: new Date().toISOString(), data: inputdata["daily_subflow_executions"] }) } } // Only for parent orgs if (childorgappRuns["data"].length > 0) { setChildOrgsAppRuns(childorgappRuns) } setSubflowRuns(subflowRuns) setWorkflowRuns(workflowRuns) setAppruns(appRuns) setApprunCosts(appcostRuns) }, [syncStats, endTime, startTime]) useEffect(() => { if (statistics && statistics?.org_id?.length > 0) { handleDataSetting(statistics, "day") } }, [statistics]) useEffect(() => { setStartTime("") setEndTime("") }, [currentTab]) const getWorkflowStats = async (workflow, startTime, endTime) => { 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 (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) { setResultLoading(false) return } // Only do latest 20 setResultLoading(true) const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime)); const allData = Promise.all(promises); if (allData === undefined || allData === null) { setResultLoading(false) } 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 } const statKey = syncStats === true ? "onprem_stats" : "daily_statistics" if (!syncStats && (statistics[statKey] === undefined || statistics[statKey] === null)) { setFilteredStatistics(statistics) setMonthlyAppRunsParent(statistics["monthly_app_executions"] ?? 0) return } // Calculate month to date cost var mtd_cost = 0 for (let key in statistics[statKey]) { const item = statistics[statKey][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 - only when no start time is selected var foundstarttime = (new Date()) if (startTime !== "" && startTime !== undefined && startTime !== null) { foundstarttime = new Date(startTime) // Set to start of day to include the entire start date foundstarttime.setHours(0, 0, 0, 0) } else { // Default to 1st of current month when no start time is selected foundstarttime.setDate(1) foundstarttime.setHours(0, 0, 0, 0) } // Set end time properly var foundendtime = (new Date()) if (endTime !== "" && endTime !== undefined && endTime !== null) { foundendtime = new Date(endTime) // Set to end of day to include the entire end date foundendtime.setHours(23, 59, 59, 999) } else { // Default to current date when no end time is selected foundendtime.setHours(23, 59, 59, 999) } // Check if start time is before the daily statistics["date"] string var newlist = [] for (let key in statistics[statKey]) { const item = statistics[statKey][key] if (item["date"] === undefined) { continue } const date = new Date(item["date"]) // Normalize the date to start of day for comparison const normalizedDate = new Date(date) normalizedDate.setHours(0, 0, 0, 0) // Normalize foundstarttime for comparison const normalizedStartTime = new Date(foundstarttime) normalizedStartTime.setHours(0, 0, 0, 0) // Normalize foundendtime for comparison const normalizedEndTime = new Date(foundendtime) normalizedEndTime.setHours(0, 0, 0, 0) if (normalizedDate >= normalizedStartTime) { if (normalizedDate <= normalizedEndTime) { 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[statKey] = newlist for (let key in newlist) { const item = newlist[key] if (item["workflow_executions"] === undefined) { continue } workflowexecutions += item["workflow_executions"] appexecutions += item["app_executions"] if (currentTab === 0 || currentTab === 3) { appexecutions += (item["child_app_executions"] ?? 0) } estimatedcost += (item["app_executions"] * invocationCost) } const today = new Date(); const isCurrentMonthSelected = (startTime === "" && endTime === "") || ( new Date(foundstarttime).getMonth() === today.getMonth() && new Date(foundstarttime).getFullYear() === today.getFullYear() && new Date(foundendtime).getMonth() === today.getMonth() && new Date(foundendtime).getFullYear() === today.getFullYear() ); if (!syncStats && isCurrentMonthSelected) { if (statistics["daily_app_executions"] !== undefined && statistics["daily_app_executions"] !== null) { appexecutions += statistics["daily_app_executions"] + (statistics["daily_child_app_executions"] ?? 0) } } tmpstats["monthly_workflow_executions"] = workflowexecutions tmpstats["monthly_app_executions"] = appexecutions if (syncStats) { setOnpremAppRuns(appexecutions) } } else { const today = new Date(); const isCurrentMonthSelected = (startTime === "" && endTime === "") || ( new Date(foundstarttime).getMonth() === today.getMonth() && new Date(foundstarttime).getFullYear() === today.getFullYear() && new Date(foundendtime).getMonth() === today.getMonth() && new Date(foundendtime).getFullYear() === today.getFullYear() ); if (!syncStats && isCurrentMonthSelected) { if (statistics["daily_app_executions"] !== undefined && statistics["daily_app_executions"] !== null) { appexecutions += statistics["daily_app_executions"] + (statistics["daily_child_app_executions"] ?? 0) } } 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 we have done monthly reset than only show monthly app runs as current month app run const currentMonth = new Date().getMonth() + 1 if (!syncStats && !monthlyAppRunsParent && statistics["monthly_app_executions"] > 0 && currentMonth === statistics["last_monthly_reset_month"]) { setMonthlyAppRunsParent(statistics["monthly_app_executions"]) } if (!syncStats && !monthlyAllSuborgExecutions && statistics["monthly_child_app_executions"]> 0 && currentMonth === statistics["last_monthly_reset_month"]) { setMonthlyAllSuborgExecutions(statistics["monthly_child_app_executions"]) } 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, syncStats, currentTab, handleDataSetting]) const handleStartTimeChange = (date) => { setStartTime(date) } const handleEndTimeChange = (date) => { setEndTime(date) } const paperStyle = { textAlign: "center", padding: "40px", margin: "5px", backgroundColor: theme.palette.cardBackgroundColor, border: theme.palette.defaultBorder, maxWidth: "300px", "&:hover": { backgroundColor: theme.palette.cardHoverColor, }, } const columns = [ { 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. {currentTab === 0 ? All Organization app runs are calculated base on addition of parent org app runs + all child org app runs. : It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform.{" "}}
{syncStats !== true ? null : "PS: You are currently looking at data from your onprem synced org"}
{filteredStatistics !== undefined ?
{/* {syncStats == true ? null : 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 } */} {/* {syncStats === true ? null : */} App runs in the selected period }> {syncStats === true ? {onpremAppRuns} : {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} } App Runs {/* } */} {syncStats === true || currentTab === 0 ? null : Workflow runs in the selected period }> {filteredStatistics.monthly_workflow_executions === null || filteredStatistics.monthly_workflow_executions === undefined ? 0 : filteredStatistics.monthly_workflow_executions} Workflow Runs } {/* {syncStats === true ? null : 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 } */}
Search from
Search until
: null}
{appRuns === undefined ? null : } {childOrgsAppRuns === undefined || currentTab === 1 ? null : } {workflowRuns === undefined || currentTab === 0? null : } {subflowRuns === undefined || currentTab === 0 ? null : } {/*appRunCosts === undefined ? null : */} {syncStats === true || currentTab === 0 ? 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;