如何构建一个React仪表盘,将工作流程和工作事件可视化

468 阅读8分钟

数据可视化是将大型数据集和指标转化为图表、图形和其他视觉效果的过程。由此产生的数据的可视化表示,使其更容易识别和分享实时趋势,异常值,以及对数据所代表的信息的新见解。使用CircleCIwebhooks,我们可以收集工作流和工作事件的数据。在本教程中,我将带领你完成创建一个基于React的仪表盘的步骤,以可视化这些数据。

前提条件

要跟上本教程,你需要对JavaScript ES6有良好的掌握。你还需要了解一些基本的React概念,如钩子和功能组件。

你将需要在你的工作站上安装这些东西。

  1. 最新的node.js安装,以及像npmyarn这样的包管理器
  2. 你喜欢的代码编辑器
  3. 一个与您的CircleCI webhook进行通信的API。你可以在这里阅读关于设置一个。
  4. 确保本教程中涉及的Laravel API CircleCI webhook已经启动并运行。

我们的教程是不分平台的,但使用CircleCI作为例子。如果你没有CircleCI账户,请**在这里注册一个免费账户。**

开始使用

使用这个命令创建一个新的React应用程序。

yarn create react-app circleci_workflow_dashboard

cd circleci_workflow_dashboard

对于这个项目,我们将使用Ant Design来布置UI,使用react-chartjs-2来显示我们的图表。

使用yarn添加项目的依赖性。

yarn add antd react-chartjs-2@3.3.0 chart.js

下一步是导入ant design的样式。用这个更新src/App.css

@import '~antd/dist/antd.css';

添加实用功能

接下来,我们需要一个与我们的API进行通信的手段。在src 文件夹中,创建一个名为utility 的新文件夹。在utility 文件夹中,创建一个名为API.js 的新文件。在src/utility/API.js 文件中,添加这个。

export const makeGETRequest = (url) => {
  return fetch(url, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  }).then((response) => response.json());
};

使用这个函数,我们可以向API发出一个GET 的请求。该函数返回来自API的JSON响应。

为了简单起见,错误处理并不包括在该函数中。

为了以可读的格式表示日期,我们可以创建一个实用函数。在utility 文件夹中,创建一个名为Date.js 的新文件并添加这个。

export const formatDate = (date) =>
  new Date(date).toLocaleString("en-GB", {
    month: "long",
    weekday: "long",
    day: "numeric",
    year: "numeric",
  });

构建仪表板组件

我们的仪表盘将显示一个事件表,你可以根据状态和事件类型进行过滤。你还可以在仪表盘的表格显示和图表显示之间进行切换。

src 文件夹中,创建一个名为components 的新文件夹。这个文件夹将存放我们将要创建的所有自定义组件。

components 目录中,创建一个名为StatusTag.jsx 的新文件。这个组件将被用来显示一个标签,其颜色由事件的状态决定。将其添加到StatusTag.jsx

import React from "react";
import { Tag } from "antd";

const StatusTag = ({ status }) => {
  const statusColours = {
    success: "green",
    failed: "volcano",
    error: "red",
    canceled: "orange",
    unauthorized: "magenta",
  };
  return <Tag color={statusColours[status]}>{status}</Tag>;
};

export default StatusTag;

接下来,在components 目录中创建一个新的文件,名为TableView.jsx 。这个组件将把通知作为一个道具,并把它们呈现在一个表格中,可以根据类型和状态进行过滤。将这段代码添加到TableView.jsx

import React from "react";
import { Table } from "antd";
import { formatDate } from "../utility/Date";
import StatusTag from "./StatusTag";

const TableView = ({ notifications }) => {
  const filterHandler = (value, record, key) => record[key] === value;

  const columns = [
    {
      title: "Subject",
      dataIndex: "commit_subject",
    },
    {
      title: "Commit Author",
      dataIndex: "commit_author",
    },
    {
      title: "Happened At",
      dataIndex: "happened_at",
      render: (text) => formatDate(text),
    },
    {
      title: "Event Type",
      dataIndex: "type",
      filters: [
        { text: "Job", value: "job-completed" },
        { text: "Workflow", value: "workflow-completed" },
      ],
      onFilter: (value, record) => filterHandler(value, record, "type"),
    },
    {
      title: "Event Status",
      dataIndex: "event_status",
      filters: [
        { text: "Success", value: "success" },
        { text: "Failed", value: "failed" },
        { text: "Error", value: "error" },
        { text: "Canceled", value: "canceled" },
        { text: "Unauthorized", value: "unauthorized" },
      ],
      render: (text) => <StatusTag status={text} />,
      onFilter: (value, record) => filterHandler(value, record, "event_status"),
    },
    {
      title: "Notification ID",
      dataIndex: "notification_id",
      render: (text, record) => (
        <a href={record["workflow_url"]} target="_blank" rel="noreferrer">
          {text}
        </a>
      ),
    },
  ];

  return (
    <Table dataSource={notifications} columns={columns} rowKey="id" bordered />
  );
};

export default TableView;

该表有六列。主题、承诺人、发生时间、事件类型、事件状态和通知ID。

每一列都由columns 常量中的一个对象表示。在不需要特殊渲染的情况下,titledataIndex 足够用于列的声明。title 被用作该列的标题,而dataIndex 条目让antd知道该列被填充了哪个属性。

为了指定要在列中渲染的组件或JSX元素,我们在列的对象表示中添加一个render 键。我们用它来渲染我们先前创建的StatusTag 组件。我们还用它来渲染Happened At 列的一个格式良好的日期,以及作为工作流链接的通知ID。

构建图表组件

在本教程中,我们将在三个图表中渲染数据。

  1. 饼状图显示事件按状态的分布:成功、失败、错误、取消或未授权。
  2. 柱状图显示事件按类型的分布情况:工作流或工作
  3. 显示事件时间线的折线图

为了建立饼状图,在components 文件夹中创建一个新文件,名为StatusDistribution.jsx ,并在其中添加这段代码。

import React from "react";
import { Pie } from "react-chartjs-2";

const StatusDistribution = ({ notifications }) => {
  const sortedNotifications = notifications.reduce(
    (sortedNotifications, notification) => {
      sortedNotifications[notification["event_status"]]++;
      return sortedNotifications;
    },
    { error: 0, failed: 0, success: 0, unauthorized: 0, canceled: 0 }
  );

  const data = {
    labels: ["Error", "Failed", "Success", "Unauthorized", "Canceled"],
    datasets: [
      {
        data: Object.values(sortedNotifications),
        backgroundColor: [
          "rgba(255, 99, 132, 0.2)",
          "rgba(255, 206, 86, 0.2)",
          "rgba(75, 192, 192, 0.2)",
          "rgba(153, 102, 255, 0.2)",
          "rgba(255, 159, 64, 0.2)",
        ],
        borderColor: [
          "rgba(255, 99, 132, 1)",
          "rgba(255, 206, 86, 1)",
          "rgba(75, 192, 192, 1)",
          "rgba(153, 102, 255, 1)",
          "rgba(255, 159, 64, 1)",
        ],
        borderWidth: 1,
      },
    ],
  };

  return (
    <>
      <div className="header">
        <h1 className="title">Status Distribution</h1>
      </div>
      <Pie data={data} height={50} />
    </>
  );
};

export default StatusDistribution;

在通知数组上使用reduce 函数,根据状态对通知进行排序和计数。然后,这些值被传递给datasets 配置中的data 键,该键被传递给Pie 组件。

为了建立条形图,在components 目录中创建一个新的文件,名为TypeDistribution.jsx ,并在其中加入这段代码。

import React from "react";
import { Bar } from "react-chartjs-2";

const TypeDistribution = ({ notifications }) => {
  const sortedNotifications = notifications.reduce(
    (sortedNotifications, notification) => {
      sortedNotifications[notification["type"]]++;
      return sortedNotifications;
    },
    { "job-completed": 0, "workflow-completed": 0 }
  );

  const data = {
    labels: ["Job", "Workflow"],
    datasets: [
      {
        label: "Event Type",
        data: Object.values(sortedNotifications),
        backgroundColor: ["rgba(54, 162, 235, 0.2)", "rgba(75, 192, 192, 0.2)"],
        borderColor: ["rgba(54, 162, 235, 1)", "rgba(75, 192, 192, 1)"],
        borderWidth: 1,
      },
    ],
  };

  const options = {
    scales: {
      y: {
        beginAtZero: true,
      },
    },
  };

  return (
    <>
      <div className="header">
        <h1 className="title">Type Distribution</h1>
      </div>
      <Bar data={data} options={options} height={500} />
    </>
  );
};

export default TypeDistribution;

就像我们对状态分布图所做的那样,我们根据通知类型对通知进行分类和计数。然后将这些值传递给datasets 配置中的data 键,再将其传递给Bar 组件。

为了建立通知的时间线,在components 目录中创建一个新的文件,名为Timeline.jsx ,并在其中添加这段代码。

import React from "react";
import { Line } from "react-chartjs-2";
import { formatDate } from "../utility/Date";

const Timeline = ({ notifications }) => {
  const sortedNotifications = notifications.reduce(
    (sortedNotifications, notification) => {
      const notificationDate = formatDate(notification["happened_at"]);
      if (notificationDate in sortedNotifications) {
        sortedNotifications[notificationDate]++;
      } else {
        sortedNotifications[notificationDate] = 1;
      }
      return sortedNotifications;
    },
    {}
  );

  const data = {
    labels: Object.keys(sortedNotifications),
    datasets: [
      {
        label: "Number of events",
        data: Object.values(sortedNotifications),
        fill: false,
        backgroundColor: "rgb(255, 99, 132)",
        borderColor: "rgba(255, 99, 132, 0.2)",
      },
    ],
  };

  const options = {
    scales: {
      y: {
        beginAtZero: true,
      },
    },
  };
  return (
    <>
      <div className="header">
        <h1 className="title">Event Timeline</h1>
      </div>
      <Line data={data} options={options} height={500} width={1500} />
    </>
  );
};

export default Timeline;

这个组件的排序功能略有不同。因为我们不能在一个初始对象中指定所有可能的日期,所以我们从一个空对象开始。然后,对于每个通知,我们检查该日期是否已经存在一个键。如果存在,我们就增加计数,如果不存在,我们就添加数值为1的日期。

接下来,我们需要建立一个组件,将所有的图表显示在一个网格中。在components 目录中创建一个新的文件,名为ChartView.jsx ,并在其中加入这段代码。

import React from "react";
import StatusDistribution from "./StatusDistribution";
import { Col, Row } from "antd";
import TypeDistribution from "./TypeDistribution";
import Timeline from "./Timeline";

const ChartView = ({ notifications }) => {
  return (
    <>
      <Timeline notifications={notifications} />
      <Row style={{ marginTop: "30px" }} gutter={96}>
        <Col>
          <StatusDistribution notifications={notifications} />
        </Col>
        <Col offset={6}>
          <TypeDistribution notifications={notifications} />
        </Col>
      </Row>
    </>
  );
};

export default ChartView;

在这个组件中,我们将状态分布和类型分布与上面的时间线并排呈现。

像我们在这里做的那样传递通知,被称为道具钻取。虽然通常不鼓励这样做,但为了简化教程,我们在这里做了这个。在生产应用中,你应该考虑适当的状态管理实现。

把它放在一起

在所有的子组件就位后,更新src/App.js ,使之与之匹配。

import "./App.css";
import { makeGETRequest } from "./utility/Api";
import { useEffect, useState } from "react";
import { Card, Col, Row, Switch } from "antd";
import TableView from "./components/TableView";
import ChartView from "./components/ChartView";

const App = () => {
  const [notifications, setNotifications] = useState([]);
  const [showTableView, setShowTableView] = useState(false);

  useEffect(() => {
    makeGETRequest("http://127.0.0.1:8000/api/circleci").then((response) => {
      setNotifications(response);
      console.log(response);
    });
  }, []);

  const handleSwitchValueChange = () => {
    setShowTableView((showTableView) => !showTableView);
  };

  return (
    <Card style={{ margin: "2%" }}>
      <Row style={{ marginBottom: "10px" }}>
        <Col span={6} offset={18}>
          Show Data as Table
          <Switch checked={showTableView} onChange={handleSwitchValueChange} />
        </Col>
      </Row>
      {showTableView ? (
        <TableView notifications={notifications} />
      ) : (
        <ChartView notifications={notifications} />
      )}
    </Card>
  );
};

export default App;

在这个组件中,我们在useEffect 钩子中从我们的API中获取通知,并使用setNotifications 函数将其保存为状态。然后我们声明一个函数来处理showTableView 状态变量的切换,它决定了数据是以表格还是图表的形式显示。

为了在不同的视图之间切换,我们渲染一个Switch ,并将showTableViewhandleSwitchValueChange 的值作为道具传递给它。

最后,根据showTableView 的值,TableViewChartView 组件被渲染。

表视图

Dashboard displaying table view

图表视图

Dashboard displaying chart view

总结

在本教程中,我们研究了如何建立一个React仪表盘,使用API可视化管道事件。通过在图表中可视化数据,我们可以对我们的管道有一个高层次的了解,也可以对数据集进行理解,无论多大规模。虽然我们没有实现这个功能,但你也可以将数据导出到电子表格中,以便进一步分析。与你的团队分享这个样本项目,并扩大你的学习范围