github.com/minio/console@v1.4.1/web-app/src/screens/Console/Dashboard/Prometheus/utils.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { Fragment } from "react";
    18  import get from "lodash/get";
    19  import { IDashboardPanel, widgetType } from "./types";
    20  import {
    21    getTimeFromTimestamp,
    22    niceBytes,
    23    niceDays,
    24    representationNumber,
    25    textToRGBColor,
    26    units,
    27  } from "../../../../common/utils";
    28  import { DiagnosticsIcon, HealIcon, UptimeIcon } from "mds";
    29  
    30  const colorsMain = [
    31    "#C4D4E9",
    32    "#DCD1EE",
    33    "#D1EEE7",
    34    "#EEDED1",
    35    "#AAF38F",
    36    "#F9E6C5",
    37    "#C83B51",
    38    "#F4CECE",
    39    "#D6D6D6",
    40  ];
    41  
    42  const niceDaysFromNS = (seconds: string) => {
    43    return niceDays(seconds, "ns");
    44  };
    45  
    46  const roundNumber = (value: string) => {
    47    return parseInt(value).toString(10);
    48  };
    49  
    50  export const panelsConfiguration: IDashboardPanel[] = [
    51    {
    52      id: 1,
    53      title: "Uptime",
    54      data: "N/A",
    55      type: widgetType.simpleWidget,
    56      widgetIcon: <UptimeIcon />,
    57      labelDisplayFunction: niceDays,
    58    },
    59    {
    60      id: 50,
    61      title: "Capacity",
    62      data: [],
    63      dataOuter: [{ name: "outer", value: 100 }],
    64      widgetConfiguration: {
    65        outerChart: {
    66          colorList: ["#9c9c9c"],
    67          innerRadius: 0,
    68          outerRadius: 0,
    69          startAngle: 0,
    70          endAngle: 0,
    71        },
    72        innerChart: {
    73          colorList: colorsMain,
    74          innerRadius: 20,
    75          outerRadius: 50,
    76          startAngle: 90,
    77          endAngle: -200,
    78        },
    79      },
    80      type: widgetType.pieChart,
    81      innerLabel: "N/A",
    82      labelDisplayFunction: niceBytes,
    83    },
    84    {
    85      id: 51,
    86      title: "Usable Capacity",
    87      data: [],
    88      dataOuter: [{ name: "outer", value: 100 }],
    89      widgetConfiguration: {
    90        outerChart: {
    91          colorList: ["#9c9c9c"],
    92          innerRadius: 0,
    93          outerRadius: 0,
    94          startAngle: 0,
    95          endAngle: 0,
    96        },
    97        innerChart: {
    98          colorList: colorsMain,
    99          innerRadius: 20,
   100          outerRadius: 50,
   101          startAngle: 90,
   102          endAngle: -200,
   103        },
   104      },
   105      type: widgetType.pieChart,
   106      innerLabel: "N/A",
   107      labelDisplayFunction: niceBytes,
   108    },
   109    {
   110      id: 68,
   111      title: "Data Usage Growth",
   112      data: [],
   113      widgetConfiguration: [
   114        {
   115          dataKey: "",
   116          keyLabel: "",
   117          lineColor: "#000",
   118          fillColor: "#000",
   119        },
   120      ],
   121      type: widgetType.areaGraph,
   122      yAxisFormatter: niceBytes,
   123      xAxisFormatter: getTimeFromTimestamp,
   124    },
   125    {
   126      id: 52,
   127      title: "Object size distribution",
   128      data: [],
   129      widgetConfiguration: [
   130        {
   131          dataKey: "a",
   132          color: "#2781B0",
   133          background: {
   134            fill: "#EEF1F4",
   135          },
   136          greatestColor: "#081C42",
   137        },
   138      ],
   139      customStructure: [
   140        { originTag: "LESS_THAN_1024_B", displayTag: "Less than 1024B" },
   141        {
   142          originTag: "BETWEEN_1024_B_AND_1_MB",
   143          displayTag: "Between 1024B and 1MB",
   144        },
   145        {
   146          originTag: "BETWEEN_1_MB_AND_10_MB",
   147          displayTag: "Between 1MB and 10MB",
   148        },
   149        {
   150          originTag: "BETWEEN_10_MB_AND_64_MB",
   151          displayTag: "Between 10MB and 64MB",
   152        },
   153        {
   154          originTag: "BETWEEN_64_MB_AND_128_MB",
   155          displayTag: "Between 64MB and 128MB",
   156        },
   157        {
   158          originTag: "BETWEEN_128_MB_AND_512_MB",
   159          displayTag: "Between 128MB and 512MB",
   160        },
   161        {
   162          originTag: "GREATER_THAN_512_MB",
   163          displayTag: "Greater than 512MB",
   164        },
   165      ],
   166      type: widgetType.barChart,
   167    },
   168    {
   169      id: 66,
   170      title: "Buckets",
   171      data: [],
   172      innerLabel: "N/A",
   173      type: widgetType.singleRep,
   174      color: "#0071BC",
   175      fillColor: "#ADD5E0",
   176    },
   177    {
   178      id: 44,
   179      title: "Objects",
   180      data: [],
   181      innerLabel: "N/A",
   182      type: widgetType.singleRep,
   183      color: "#0071BC",
   184      fillColor: "#ADD5E0",
   185    },
   186    {
   187      id: 63,
   188      title: "API Data Received Rate",
   189      data: [],
   190      widgetConfiguration: [
   191        {
   192          dataKey: "",
   193          keyLabel: "",
   194          lineColor: "#000",
   195          fillColor: "#000",
   196          strokeWidth: 3,
   197        },
   198      ],
   199      type: widgetType.linearGraph,
   200  
   201      xAxisFormatter: getTimeFromTimestamp,
   202      yAxisFormatter: niceBytes,
   203    },
   204    {
   205      id: 61,
   206      title: "Total Open FDs",
   207      data: [],
   208      innerLabel: "N/A",
   209      type: widgetType.singleRep,
   210      color: "#22B573",
   211      fillColor: "#A6E8C4",
   212    },
   213    {
   214      id: 62,
   215      title: "Total Goroutines",
   216      data: [],
   217      innerLabel: "N/A",
   218      type: widgetType.singleRep,
   219      color: "#F7655E",
   220      fillColor: "#F4CECE",
   221    },
   222    {
   223      id: 77,
   224      title: "Node CPU Usage",
   225      data: [],
   226      widgetConfiguration: [
   227        {
   228          dataKey: "",
   229          keyLabel: "",
   230          lineColor: "#000",
   231          fillColor: "#000",
   232        },
   233      ],
   234      type: widgetType.linearGraph,
   235  
   236      yAxisFormatter: roundNumber,
   237      xAxisFormatter: getTimeFromTimestamp,
   238    },
   239    {
   240      id: 60,
   241      title: "API Request Rate",
   242      data: [],
   243      widgetConfiguration: [
   244        {
   245          dataKey: "",
   246          keyLabel: "",
   247          lineColor: "#000",
   248          fillColor: "#000",
   249        },
   250      ],
   251      type: widgetType.linearGraph,
   252      yAxisFormatter: roundNumber,
   253      xAxisFormatter: getTimeFromTimestamp,
   254    },
   255    {
   256      id: 70,
   257      title: "API Data Sent Rate",
   258      data: [],
   259      widgetConfiguration: [
   260        {
   261          dataKey: "",
   262          keyLabel: "",
   263          lineColor: "#000",
   264          fillColor: "#000",
   265        },
   266      ],
   267      type: widgetType.linearGraph,
   268  
   269      xAxisFormatter: getTimeFromTimestamp,
   270      yAxisFormatter: niceBytes,
   271    },
   272    {
   273      id: 17,
   274      title: "Internode Data Transfer",
   275      data: [],
   276      widgetConfiguration: [
   277        {
   278          dataKey: "",
   279          keyLabel: "",
   280          lineColor: "#000",
   281          fillColor: "#000",
   282        },
   283      ],
   284      type: widgetType.linearGraph,
   285  
   286      yAxisFormatter: niceBytes,
   287      xAxisFormatter: getTimeFromTimestamp,
   288    },
   289    {
   290      id: 73,
   291      title: "Node IO",
   292      data: [],
   293      widgetConfiguration: [
   294        {
   295          dataKey: "",
   296          keyLabel: "",
   297          lineColor: "#000",
   298          fillColor: "#000",
   299        },
   300      ],
   301      type: widgetType.linearGraph,
   302  
   303      yAxisFormatter: niceBytes,
   304      xAxisFormatter: getTimeFromTimestamp,
   305    },
   306    {
   307      id: 80,
   308      title: "Time Since Last Heal Activity",
   309      data: "N/A",
   310      type: widgetType.simpleWidget,
   311      widgetIcon: <HealIcon />,
   312      labelDisplayFunction: niceDaysFromNS,
   313    },
   314    {
   315      id: 81,
   316      title: "Time Since Last Scan Activity",
   317      data: "N/A",
   318      type: widgetType.simpleWidget,
   319      widgetIcon: <DiagnosticsIcon />,
   320      labelDisplayFunction: niceDaysFromNS,
   321    },
   322    {
   323      id: 71,
   324      title: "API Request Error Rate",
   325      data: [],
   326      widgetConfiguration: [
   327        {
   328          dataKey: "",
   329          keyLabel: "",
   330          lineColor: "#000",
   331          fillColor: "#000",
   332        },
   333      ],
   334      type: widgetType.linearGraph,
   335  
   336      xAxisFormatter: getTimeFromTimestamp,
   337    },
   338    {
   339      id: 76,
   340      title: "Node Memory Usage",
   341      data: [],
   342      widgetConfiguration: [
   343        {
   344          dataKey: "",
   345          keyLabel: "",
   346          lineColor: "#000",
   347          fillColor: "#000",
   348        },
   349      ],
   350      type: widgetType.linearGraph,
   351  
   352      xAxisFormatter: getTimeFromTimestamp,
   353      yAxisFormatter: niceBytes,
   354    },
   355    {
   356      id: 74,
   357      title: "Drive Used Capacity",
   358      data: [],
   359      widgetConfiguration: [
   360        {
   361          dataKey: "",
   362          keyLabel: "",
   363          lineColor: "#000",
   364          fillColor: "#000",
   365        },
   366      ],
   367      type: widgetType.linearGraph,
   368  
   369      xAxisFormatter: getTimeFromTimestamp,
   370      yAxisFormatter: niceBytes,
   371    },
   372    {
   373      id: 82,
   374      title: "Drives Free Inodes",
   375      data: [],
   376      widgetConfiguration: [
   377        {
   378          dataKey: "",
   379          keyLabel: "",
   380          lineColor: "#000",
   381          fillColor: "#000",
   382        },
   383      ],
   384      type: widgetType.linearGraph,
   385  
   386      disableYAxis: true,
   387      xAxisFormatter: getTimeFromTimestamp,
   388    },
   389    {
   390      id: 11,
   391      title: "Node Syscalls",
   392      data: [],
   393      widgetConfiguration: [
   394        {
   395          dataKey: "",
   396          keyLabel: "",
   397          lineColor: "#000",
   398          fillColor: "#000",
   399        },
   400      ],
   401      type: widgetType.linearGraph,
   402      yAxisFormatter: roundNumber,
   403      xAxisFormatter: getTimeFromTimestamp,
   404    },
   405    {
   406      id: 8,
   407      title: "Node File Descriptors",
   408      data: [],
   409      widgetConfiguration: [
   410        {
   411          dataKey: "",
   412          keyLabel: "",
   413          lineColor: "#000",
   414          fillColor: "#000",
   415        },
   416      ],
   417      type: widgetType.linearGraph,
   418      yAxisFormatter: roundNumber,
   419      xAxisFormatter: getTimeFromTimestamp,
   420    },
   421    {
   422      id: 500,
   423      mergedPanels: [
   424        {
   425          id: 53,
   426          title: "Online",
   427          data: "N/A",
   428          type: widgetType.singleValue,
   429        },
   430        {
   431          id: 69,
   432          title: "Offline",
   433          data: "N/A",
   434          type: widgetType.singleValue,
   435        },
   436      ],
   437      title: "Servers",
   438    },
   439    {
   440      id: 501,
   441      mergedPanels: [
   442        {
   443          id: 9,
   444          title: "Online",
   445          data: "N/A",
   446          type: widgetType.singleValue,
   447        },
   448        {
   449          id: 78,
   450          title: "Offline",
   451          data: "N/A",
   452          type: widgetType.singleValue,
   453        },
   454      ],
   455      title: "Drives",
   456    },
   457    {
   458      id: 502,
   459      mergedPanels: [
   460        {
   461          id: 65,
   462          title: "Upload",
   463          data: "N/A",
   464          type: widgetType.singleValue,
   465  
   466          labelDisplayFunction: niceBytes,
   467        },
   468        {
   469          id: 64,
   470          title: "Download",
   471          data: "N/A",
   472          type: widgetType.singleValue,
   473  
   474          labelDisplayFunction: niceBytes,
   475        },
   476      ],
   477      title: "Network",
   478    },
   479  ];
   480  
   481  const calculateMainValue = (elements: any[], metricCalc: string) => {
   482    if (elements.length === 0) {
   483      return ["", "0"];
   484    }
   485  
   486    switch (metricCalc) {
   487      case "mean":
   488        const sumValues = elements.reduce((accumulator, currValue) => {
   489          return accumulator + parseFloat(currValue[1]);
   490        }, 0);
   491  
   492        const mean = Math.floor(sumValues / elements.length);
   493  
   494        return ["", mean.toString()];
   495      default:
   496        const sortResult = elements.sort(
   497          (value1: any[], value2: any[]) => value1[0] - value2[0],
   498        );
   499  
   500        return sortResult[sortResult.length - 1];
   501    }
   502  };
   503  
   504  const constructLabelNames = (metrics: any, legendFormat: string) => {
   505    const keysToReplace = Object.keys(metrics);
   506    const expToReplace = new RegExp(`{{(${keysToReplace.join("|")})}}`, "g");
   507  
   508    let replacedLegend = legendFormat.replace(expToReplace, (matchItem) => {
   509      const nwMatchItem = matchItem.replace(/({{|}})/g, "");
   510      return metrics[nwMatchItem];
   511    });
   512  
   513    const countVarsOpen = (replacedLegend.match(/{{/g) || []).length;
   514    const countVarsClose = (replacedLegend.match(/}}/g) || []).length;
   515  
   516    let cleanLegend = replacedLegend.replace(/{{(.*?)}}/g, "");
   517  
   518    if (
   519      countVarsOpen === countVarsClose &&
   520      countVarsOpen !== 0 &&
   521      countVarsClose !== 0
   522    ) {
   523      keysToReplace.forEach((element) => {
   524        replacedLegend = replacedLegend.replace(element, metrics[element]);
   525      });
   526  
   527      cleanLegend = replacedLegend;
   528    }
   529  
   530    // In case not all the legends were replaced, we remove the placeholders.
   531    return cleanLegend;
   532  };
   533  
   534  export const widgetDetailsToPanel = (
   535    payloadData: any,
   536    panelItem: IDashboardPanel,
   537  ) => {
   538    if (!payloadData) {
   539      return panelItem;
   540    }
   541  
   542    const typeOfPayload = payloadData.type;
   543  
   544    switch (panelItem.type) {
   545      case widgetType.singleValue:
   546      case widgetType.simpleWidget:
   547        if (typeOfPayload === "stat" || typeOfPayload === "singlestat") {
   548          // We sort values & get the last value
   549          let elements = get(payloadData, "targets[0].result[0].values", []);
   550  
   551          if (elements === null) {
   552            elements = [];
   553          }
   554  
   555          const metricCalc = get(
   556            payloadData,
   557            "options.reduceOptions.calcs[0]",
   558            "lastNotNull",
   559          );
   560  
   561          const valueDisplay = calculateMainValue(elements, metricCalc);
   562  
   563          const data = panelItem.labelDisplayFunction
   564            ? panelItem.labelDisplayFunction(valueDisplay[1])
   565            : valueDisplay[1];
   566  
   567          return {
   568            ...panelItem,
   569            data,
   570          };
   571        }
   572        break;
   573      case widgetType.pieChart:
   574        if (typeOfPayload === "gauge") {
   575          const metricCalc = get(
   576            payloadData,
   577            "options.reduceOptions.calcs[0]",
   578            "lastNotNull",
   579          );
   580  
   581          let chartSeries = get(payloadData, "targets", []).filter(
   582            (seriesItem: any) => seriesItem !== null,
   583          );
   584  
   585          const values = chartSeries.map((chartTarget: any) => {
   586            const resultMap =
   587              chartTarget.result && Array.isArray(chartTarget.result)
   588                ? chartTarget.result
   589                : [];
   590  
   591            const values = resultMap.map((elementValue: any) => {
   592              const values = get(elementValue, "values", []);
   593              const metricKeyItem = Object.keys(elementValue.metric);
   594              const sortResult = values.sort(
   595                (value1: any[], value2: any[]) =>
   596                  parseInt(value1[0][1]) - parseInt(value2[0][1]),
   597              );
   598  
   599              const metricName = elementValue.metric[metricKeyItem[0]];
   600              const value = sortResult[sortResult.length - 1];
   601              return {
   602                name: metricName,
   603                value: parseInt(value[1]),
   604                legend: chartTarget.legendFormat,
   605              };
   606            });
   607  
   608            return values;
   609          });
   610  
   611          const firstTarget =
   612            chartSeries[0].result && chartSeries[0].result.length > 0
   613              ? chartSeries[0].result[0].values
   614              : [];
   615  
   616          const totalValues = calculateMainValue(firstTarget, metricCalc);
   617  
   618          const innerLabel = panelItem.labelDisplayFunction
   619            ? panelItem.labelDisplayFunction(totalValues[1])
   620            : totalValues[1];
   621  
   622          return {
   623            ...panelItem,
   624            data: values,
   625            innerLabel,
   626          };
   627        }
   628        break;
   629      case widgetType.linearGraph:
   630      case widgetType.areaGraph:
   631        if (typeOfPayload === "graph") {
   632          let targets = get(payloadData, "targets", []);
   633          if (targets === null) {
   634            targets = [];
   635          }
   636  
   637          const series: any[] = [];
   638          const plotValues: any[] = [];
   639  
   640          targets.forEach(
   641            (
   642              targetMaster: { legendFormat: string; result: any[] },
   643              index: number,
   644            ) => {
   645              // Add a new serie to plot variables in case it is not from multiple values
   646              let results = get(targetMaster, "result", []);
   647              const legendFormat = targetMaster.legendFormat;
   648              if (results === null) {
   649                results = [];
   650              }
   651  
   652              results.forEach((itemVals: { metric: object; values: any[] }) => {
   653                // Label Creation
   654                const labelName = constructLabelNames(
   655                  itemVals.metric,
   656                  legendFormat,
   657                );
   658                const keyName = `key_${index}${labelName}`;
   659  
   660                // series creation with recently created label
   661                series.push({
   662                  dataKey: keyName,
   663                  keyLabel: labelName,
   664                  lineColor: "",
   665                  fillColor: "",
   666                });
   667  
   668                // we iterate over values and create elements
   669                let values = get(itemVals, "values", []);
   670                if (values === null) {
   671                  values = [];
   672                }
   673  
   674                values.forEach((valInfo: any[]) => {
   675                  const itemIndex = plotValues.findIndex(
   676                    (element) => element.name === valInfo[0],
   677                  );
   678  
   679                  // Element not exists yet
   680                  if (itemIndex === -1) {
   681                    let itemToPush: any = { name: valInfo[0] };
   682                    itemToPush[keyName] = valInfo[1];
   683  
   684                    plotValues.push(itemToPush);
   685                  } else {
   686                    plotValues[itemIndex][keyName] = valInfo[1];
   687                  }
   688                });
   689              });
   690            },
   691          );
   692  
   693          const sortedSeries = series.sort((series1: any, series2: any) => {
   694            if (series1.keyLabel < series2.keyLabel) {
   695              return -1;
   696            }
   697            if (series1.keyLabel > series2.keyLabel) {
   698              return 1;
   699            }
   700            return 0;
   701          });
   702  
   703          const seriesWithColors = sortedSeries.map(
   704            (serialC: any, index: number) => {
   705              return {
   706                ...serialC,
   707                lineColor: colorsMain[index] || textToRGBColor(serialC.keyLabel),
   708                fillColor: colorsMain[index] || textToRGBColor(serialC.keyLabel),
   709              };
   710            },
   711          );
   712  
   713          const sortedVals = plotValues.sort(
   714            (value1: any, value2: any) => value1.name - value2.name,
   715          );
   716  
   717          return {
   718            ...panelItem,
   719            widgetConfiguration: seriesWithColors,
   720            data: sortedVals,
   721          };
   722        }
   723        break;
   724      case widgetType.barChart:
   725        if (typeOfPayload === "bargauge") {
   726          let chartBars = get(payloadData, "targets[0].result", []);
   727  
   728          if (chartBars === null) {
   729            chartBars = [];
   730          }
   731  
   732          const sortFunction = (value1: any[], value2: any[]) =>
   733            value1[0] - value2[0];
   734  
   735          let values = [];
   736          if (panelItem.customStructure) {
   737            values = panelItem.customStructure.map((structureItem) => {
   738              const metricTake = chartBars.find((element: any) => {
   739                return element.metric.range === structureItem.originTag;
   740              });
   741  
   742              const elements = get(metricTake, "values", []);
   743  
   744              const sortResult = elements.sort(sortFunction);
   745              const lastValue = sortResult[sortResult.length - 1] || ["", "0"];
   746  
   747              return {
   748                name: structureItem.displayTag,
   749                a: parseInt(lastValue[1]),
   750              };
   751            });
   752          } else {
   753            // If no configuration is set, we construct the series for bar chart and return the element
   754            values = chartBars.map((elementValue: any) => {
   755              const metricKeyItem = Object.keys(elementValue.metric);
   756  
   757              const metricName = elementValue.metric[metricKeyItem[0]];
   758  
   759              const elements = get(elementValue, "values", []);
   760  
   761              const sortResult = elements.sort(sortFunction);
   762              const lastValue = sortResult[sortResult.length - 1] || ["", "0"];
   763              return { name: metricName, a: parseInt(lastValue[1]) };
   764            });
   765          }
   766  
   767          return {
   768            ...panelItem,
   769            data: values,
   770          };
   771        }
   772        break;
   773      case widgetType.singleRep:
   774        if (typeOfPayload === "stat") {
   775          // We sort values & get the last value
   776          let elements = get(payloadData, "targets[0].result[0].values", []);
   777          if (elements === null) {
   778            elements = [];
   779          }
   780          const metricCalc = get(
   781            payloadData,
   782            "options.reduceOptions.calcs[0]",
   783            "lastNotNull",
   784          );
   785  
   786          const valueDisplay = calculateMainValue(elements, metricCalc);
   787  
   788          const sortResult = elements.sort(
   789            (value1: any[], value2: any[]) => value1[0] - value2[0],
   790          );
   791  
   792          let valuesForBackground = [];
   793  
   794          if (sortResult.length === 1) {
   795            valuesForBackground.push({ value: 0 });
   796          }
   797  
   798          sortResult.forEach((eachVal: any) => {
   799            valuesForBackground.push({ value: parseInt(eachVal[1]) });
   800          });
   801  
   802          const innerLabel = panelItem.labelDisplayFunction
   803            ? panelItem.labelDisplayFunction(valueDisplay[1])
   804            : valueDisplay[1];
   805  
   806          return {
   807            ...panelItem,
   808            data: valuesForBackground,
   809            innerLabel,
   810          };
   811        }
   812        break;
   813    }
   814  
   815    return panelItem;
   816  };
   817  
   818  const verifyNumeric = (item: string) => {
   819    return !isNaN(parseFloat(item));
   820  };
   821  
   822  export const splitSizeMetric = (val: string) => {
   823    const splittedText = val.split(" ");
   824    // Value is not a size metric, we return as common string
   825  
   826    const singleValue = () => {
   827      let vl = val;
   828  
   829      if (verifyNumeric(val)) {
   830        vl = representationNumber(parseFloat(val));
   831      }
   832      return <Fragment>{vl}</Fragment>;
   833    };
   834  
   835    if (splittedText.length !== 2) {
   836      return singleValue();
   837    }
   838  
   839    if (!units.includes(splittedText[1])) {
   840      return singleValue();
   841    }
   842  
   843    return (
   844      <span className="commonValue">
   845        {splittedText[0]}
   846        <span className="unitText">{splittedText[1]}</span>
   847      </span>
   848    );
   849  };