github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/ui/dashboard/src/components/dashboards/layout/PanelDetail/PanelDetailLog.tsx (about)

     1  import DateTime from "../../../DateTime";
     2  import Icon from "../../../Icon";
     3  import Panel from "../Panel";
     4  import sortBy from "lodash/sortBy";
     5  import { classNames } from "../../../../utils/styles";
     6  import {
     7    DashboardRunState,
     8    PanelDefinition,
     9    PanelLog,
    10    PanelsLog,
    11    PanelsMap,
    12  } from "../../../../types";
    13  import { Disclosure } from "@headlessui/react";
    14  import { getNodeAndEdgeDataFormat } from "../../common/useNodeAndEdgeData";
    15  import { NodeAndEdgeProperties } from "../../common/types";
    16  import { PanelDetailProps } from "./index";
    17  import { useDashboard } from "../../../../hooks/useDashboard";
    18  import { usePanel } from "../../../../hooks/usePanel";
    19  
    20  type PanelLogRowProps = {
    21    log: PanelLog;
    22  };
    23  
    24  type PanelLogIconProps = {
    25    status: DashboardRunState;
    26  };
    27  
    28  type PanelLogStatusProps = {
    29    status: DashboardRunState;
    30  };
    31  
    32  const PanelLogIcon = ({ status }: PanelLogIconProps) => {
    33    switch (status) {
    34      case "initialized":
    35        return (
    36          <Icon
    37            className="text-skip w-4.5 h-4.5"
    38            icon="materialsymbols-outline:pending"
    39          />
    40        );
    41      case "blocked":
    42        return (
    43          <Icon
    44            className="text-skip w-4.5 h-4.5"
    45            icon="materialsymbols-solid:block"
    46          />
    47        );
    48      case "running":
    49        return (
    50          <Icon
    51            className="text-skip w-4.5 h-4.5"
    52            icon="materialsymbols-solid:run_circle"
    53          />
    54        );
    55      case "cancelled":
    56        return (
    57          <Icon
    58            className="text-skip w-4.5 h-4.5"
    59            icon="materialsymbols-outline:cancel"
    60          />
    61        );
    62      case "error":
    63        return (
    64          <Icon
    65            className="text-alert w-4.5 h-4.5"
    66            icon="materialsymbols-solid:error"
    67          />
    68        );
    69      case "complete":
    70        return (
    71          <Icon
    72            className="text-ok w-4.5 h-4.5"
    73            icon="materialsymbols-solid:check_circle"
    74          />
    75        );
    76    }
    77  };
    78  
    79  const PanelLogStatus = ({ status }: PanelLogStatusProps) => {
    80    const baseClassname = "inline-block tabular-nums whitespace-nowrap";
    81    switch (status) {
    82      case "initialized":
    83        return <pre className={baseClassname}>Initialized</pre>;
    84      case "blocked":
    85        return (
    86          <pre className={baseClassname}>Blocked&nbsp;&nbsp;&nbsp;&nbsp;</pre>
    87        );
    88      case "running":
    89        return (
    90          <pre className={baseClassname}>Running&nbsp;&nbsp;&nbsp;&nbsp;</pre>
    91        );
    92      case "cancelled":
    93        return <pre className={baseClassname}>Cancelled&nbsp;&nbsp;</pre>;
    94      case "error":
    95        return (
    96          <pre className={baseClassname}>
    97            Error&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    98          </pre>
    99        );
   100      case "complete":
   101        return <pre className={baseClassname}>Complete&nbsp;&nbsp;&nbsp;</pre>;
   102    }
   103  };
   104  
   105  const PanelLogMessage = ({ log }: PanelLogRowProps) => (
   106    <div className="flex space-x-2">
   107      <PanelLogStatus status={log.status} />
   108      {log.prefix && (
   109        <pre className="text-foreground-lighter tabular-nums">{log.prefix}:</pre>
   110      )}
   111      {log.isDependency && <span className="">{log.title}</span>}
   112      {log.executionTime !== undefined && (
   113        <span className="text-foreground-lighter tabular-nums">
   114          {log.executionTime.toLocaleString()}ms
   115        </span>
   116      )}
   117    </div>
   118  );
   119  
   120  const PanelLogRow = ({ log }: PanelLogRowProps) => {
   121    return (
   122      <Disclosure>
   123        {({ open }) => {
   124          return (
   125            <>
   126              <Disclosure.Button
   127                className={classNames(
   128                  "w-full px-2 py-1 flex justify-between items-center hover:bg-black-scale-2",
   129                  log.error ? "cursor-pointer" : "cursor-default"
   130                )}
   131              >
   132                <div className="flex items-center space-x-3">
   133                  <PanelLogIcon status={log.status} />
   134                  <DateTime
   135                    date={log.timestamp}
   136                    dateClassName="hidden"
   137                    timeFormat="HH:mm:ss.SSS"
   138                  />
   139                  <PanelLogMessage log={log} />
   140                </div>
   141                {log.error ? (
   142                  <div>
   143                    <Icon
   144                      className="w-4.5 h-4.5 text-foreground-light"
   145                      icon={
   146                        open
   147                          ? "materialsymbols-outline:expand_less"
   148                          : "materialsymbols-outline:expand_more"
   149                      }
   150                    />
   151                  </div>
   152                ) : null}
   153              </Disclosure.Button>
   154              {log.error ? (
   155                <Disclosure.Panel className="px-2 py-1">
   156                  {log.error}
   157                </Disclosure.Panel>
   158              ) : null}
   159            </>
   160          );
   161        }}
   162      </Disclosure>
   163    );
   164  };
   165  
   166  const addDependencyLogs = (
   167    panel: PanelDefinition,
   168    panelsLog: PanelsLog,
   169    panelsMap: PanelsMap,
   170    dependencyLogs: PanelLog[],
   171    dependentPanelRecord
   172  ) => {
   173    for (const dependency of panel.dependencies || []) {
   174      if (dependentPanelRecord[dependency]) {
   175        continue;
   176      }
   177      dependentPanelRecord[dependency] = true;
   178      const dependencyPanel = panelsMap[dependency];
   179      if (!dependencyPanel) {
   180        continue;
   181      }
   182      addDependencyLogs(
   183        dependencyPanel,
   184        panelsLog,
   185        panelsMap,
   186        dependencyLogs,
   187        dependentPanelRecord
   188      );
   189    }
   190    const dependencyPanelLog = panelsLog[panel.name];
   191    dependencyLogs.push(
   192      ...dependencyPanelLog.map((l) => ({
   193        ...l,
   194        isDependency: true,
   195        prefix: panel.panel_type,
   196      }))
   197    );
   198  };
   199  
   200  const getDependencyLogs = (
   201    panel: PanelDefinition,
   202    panelsLog: PanelsLog,
   203    panelsMap: PanelsMap
   204  ) => {
   205    const dependencyLogs: PanelLog[] = [];
   206    const dependentPanelRecord = {};
   207  
   208    addDependencyLogs(
   209      panel,
   210      panelsLog,
   211      panelsMap,
   212      dependencyLogs,
   213      dependentPanelRecord
   214    );
   215  
   216    if (
   217      (panel.panel_type === "flow" ||
   218        panel.panel_type === "graph" ||
   219        panel.panel_type === "hierarchy") &&
   220      panel.properties &&
   221      getNodeAndEdgeDataFormat(panel.properties) === "NODE_AND_EDGE"
   222    ) {
   223      const nodeAndEdgeProperties = panel.properties as NodeAndEdgeProperties;
   224      for (const node of nodeAndEdgeProperties.nodes || []) {
   225        const nodePanel = panelsMap[node];
   226        if (!nodePanel) {
   227          continue;
   228        }
   229        addDependencyLogs(
   230          nodePanel,
   231          panelsLog,
   232          panelsMap,
   233          dependencyLogs,
   234          dependentPanelRecord
   235        );
   236      }
   237      for (const edge of nodeAndEdgeProperties.edges || []) {
   238        const edgePanel = panelsMap[edge];
   239        if (!edgePanel) {
   240          continue;
   241        }
   242        addDependencyLogs(
   243          edgePanel,
   244          panelsLog,
   245          panelsMap,
   246          dependencyLogs,
   247          dependentPanelRecord
   248        );
   249      }
   250    }
   251    return dependencyLogs;
   252  };
   253  
   254  const PanelLogs = () => {
   255    const { panelsLog, panelsMap } = useDashboard();
   256    const { definition } = usePanel();
   257    const panelLog = panelsLog[definition.name];
   258    const dependencyPanelLogs = getDependencyLogs(
   259      definition as PanelDefinition,
   260      panelsLog,
   261      panelsMap
   262    );
   263    const allLogs = sortBy([...dependencyPanelLogs, ...panelLog], "timestamp");
   264    return (
   265      <div className="border border-black-scale-2 divide-y divide-divide">
   266        {allLogs.map((log, idx) => (
   267          <PanelLogRow
   268            key={`${log.status}:${log.timestamp}:${log.prefix}:${log.title}-${idx}`}
   269            log={log}
   270          />
   271        ))}
   272      </div>
   273    );
   274  };
   275  
   276  const PanelDetailLog = ({ definition }: PanelDetailProps) => (
   277    <Panel
   278      definition={{
   279        ...definition,
   280        title: `${definition.title ? `${definition.title} Log` : "Log"}`,
   281      }}
   282      parentType="dashboard"
   283      showControls={false}
   284      showPanelError={false}
   285      forceBackground={true}
   286    >
   287      <PanelLogs />
   288    </Panel>
   289  );
   290  
   291  export default PanelDetailLog;