ソフトウェア開発の最適化:GridDB を用いたバージョン管理システムの活動監視

概要

現在の競争の激しいビジネス環境において、高品質な製品を迅速に提供するためには、効率的なソフトウェア開発が不可欠です。この目標を達成するための重要な要素の一つが、バージョン管理システム(VCS)における開発者の活動を監視し、コラボレーションとコードの品質を最適化することです。コードのコミット、プルリクエスト、マージ、ブランチングなどのパターンを分析することで、組織はボトルネックを特定し、ワークフローを改善し、最終的に生産性を向上させることができます。

Git、SVN、GitLabなどの人気のVCSツールは変更を追跡できますが、大規模な開発活動を時間軸で分析することは困難です。ここでGridDB、高性能な時系列データベースが真価を発揮します。大量のタイムセンシティブデータを管理するために設計されたGridDBは、VCSイベントを効率的にキャプチャ、保存、分析可能です。

VCS活動監視にGridDBを使う理由

タイムスタンプ付きデータの処理 VCS内のコミット、プルリクエスト、マージイベントは、それぞれ特定のタイムスタンプと関連付けられています。GridDBは時系列データ処理に最適化されており、時間依存のデベロッパー活動を捕捉し分析するのに理想的です。

スケーラビリティ 大規模な分散チームでは、1日あたり数千件のコミットやプルリクエストが発生する可能性があります。GridDBのスケーラブルなアーキテクチャは、大量のVCSイベントを効率的に取り込み、クエリ実行時のパフォーマンス低下なしに処理できます。

効率的なデータ取得 GridDBは時間ベースのクエリを可能にし、特定の期間(例:日次、週次、月次)のデベロッパー活動を迅速に取得できます。これにより、チームのプロダクティビティやコラボレーションのリアルタイムな洞察を得られます。

過去の洞察 過去のイベント(例:コード衝突の急増や活動が低い期間)を分析することで、チームはより効率的なワークフローにつながるパターンを発見できます。GridDBの過去のデータを保存しクエリする機能は、開発トレンドの深い分析を可能にします。

このブログでは、シンプルなSpring Bootプロジェクトを使用してGridDBでバージョン管理システム(VCS)のアクティビティを監視する方法を探ります。具体的には、以下の主要な開発イベントを追跡します:

  • コードコミット
  • プルリクエスト
  • コードマージ

GridDBでこのデータを保存し分析することで、ソフトウェアチームやプロジェクトマネージャーは:

  • コードの変更履歴を追跡することで開発者の生産性を監視できます。
  • マージ衝突や活動の高低期などのパターンを特定する。
  • ブランチ戦略やプルリクエストのワークフローを評価して協業を最適化する。
  • 歴史的分析を実施し、長期的な開発トレンドと実践を改善する。

GridDBクラスターとSpring Bootの統合:リアルタイム監視のための設定

最初のステップは、GridDBクラスターを設定し、Spring Bootアプリケーションと統合することです。

  • このプロジェクトのソースコード

GridDBnet ブログのリポジトリをクローンし、ソースコード用の適切なブランチを取得します。

$ git clone https://github.com/griddbnet/Blogs.git --branch vcs

  • GridDB クラスターの設定

GridDBは、さまざまな要件に対応するための柔軟なオプションを提供しています。開発段階では、ローカルマシン上の単一ノードクラスターで十分かもしれません。ただし、本番環境では、障害耐性とスケーラビリティを向上させるため、複数のマシンに分散したクラスターが一般的に推奨されます。クラスターのセットアップに関する詳細なガイドラインは、GridDBのドキュメントを参照してください。

GridDBクラスターをセットアップするには、以下の手順に従ってください こちら.

  • Spring Boot アプリケーションのセットアップ

GridDB クラスターが正常に動作している場合、次のステップはこれを Spring Boot アプリケーションに接続することです。GridDB Java Client API は、この接続を確立するための必要なツールを提供します。プロセスを簡素化するため、プロジェクトの依存関係として griddb-spring-boot-starter ライブラリを含めることができます。このライブラリは、接続設定を簡素化する事前設定済みの Bean を提供します。

プロジェクト構造

以下は、このようなアプリケーションのための推奨されるプロジェクト構造です:

├───my-griddb-app
│   │   pom.xml
│   │   
│   ├───src
│   │   ├───main
│   │   │   ├───java
│   │   │   │   └───mycode
│   │   │   │       │   MySpringBootApplication.java
│   │   │   │       │
│   │   │   │       ├───config
│   │   │   │       │       GridDBConfig.java
│   │   │   │       │
│   │   │   │       ├───controller
│   │   │   │       │       ChartController.java
│   │   │   │       │
│   │   │   │       ├───dto
│   │   │   │       │       VcsActivityDTO.java
│   │   │   │       │
│   │   │   │       └───service
│   │   │   │               ChartService.java
│   │   │   │               MetricsCollectionService.java
│   │   │   │               RestTemplateConfig.java
│   │   │   │
│   │   │   └───resources
│   │   │       │   application.properties
│   │   │       │
│   │   │       └───templates
│   │   │               charts.html

この構造は、コントローラー、モデル、リポジトリ、サービス、およびアプリケーションのエントリポイントを独立した層に分離し、モジュール性と保守性を向上させます。

GridDB 依存関係の追加

Spring Boot プロジェクトで GridDB と相互作用できるようにするには、GridDB Java Client API 依存関係を含める必要があります。これには、プロジェクトのビルドファイル(Maven の pom.xml または Gradle の相当するファイル)に適切な設定を追加します。

pom.xml ファイルでの依存関係の設定例:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>my-griddb-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>my-griddb-app</name>
  <url>http://maven.apache.org</url>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.github.griddb</groupId>
      <artifactId>gridstore</artifactId>
      <version>5.6.0</version>
    </dependency>
    <!-- Spring Boot dependencies -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- JSON processing -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.15.0</version> <!-- or the latest version -->
    </dependency>
    <!-- Lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>
</project>

GitHub API アクセス トークンを生成する

GitHub のデータ(コミットやプルリクエストなど)にアクセスするには、GitHub 個人用アクセス トークンが必要です。公式の GitHub ドキュメントの手順に従って作成してください:

  1. GitHub にログインします。
  2. GitHub 個人アクセス トークン ドキュメントに移動します。
  3. 必要なスコープ(repouser)を含むトークンを生成する手順に従います。

GridDB接続の設定

GridDB依存関係を追加した後、次のステップはSpring BootアプリケーションでGridDBクラスターの接続詳細を設定することです。これは通常、アプリケーションの設定を指定するapplication.propertiesファイルで設定されます。

接続詳細を設定する例を以下に示します:

GRIDDB_NOTIFICATION_MEMBER=127.0.0.1:10001
GRIDDB_CLUSTER_NAME=myCluster
GRIDDB_USER=admin
GRIDDB_PASSWORD=admin
management.endpoints.web.exposure.include=*
server.port=9090


# GitHub API token and configuration
github.api.token=github_DXXXXXXXXXXXXA5OyZwdXr
github.api.base-url=https://api.github.com
  • griddb.cluster.host: GridDB クラスターのホスト名または IP アドレス。
  • griddb.cluster.port: GridDB クラスターがリスニングしているポート番号
  • griddb.cluster.user: GridDB クラスターにアクセスするためのユーザー名。
  • griddb.cluster.password: 指定した GridDB ユーザーのパスワード(実際のパスワードに置き換えてください)。
  • server.port=9090: Spring Boot アプリケーションが実行されるポートを設定します。
  • github.api.token: GitHub API への認証に使用する個人用アクセス トークン(生成済み)
  • github.api.base-url: GitHub API へのリクエスト送信用のベース URL(例: https://api.github.com)。

GridDB クライアント Bean の作成

Spring Boot アプリケーションで GridDB と効果的に連携するためには、GridDB 接続を管理する専用の Spring Bean を作成する必要があります。この Bean は、application.properties ファイルで定義されたパラメーターを使用して接続を確立し、アプリケーション全体で GridDB クラスターとやり取りするための中央インターフェースとして機能します。

以下は、GridDbConfig.java という名前の Java クラスでこの Bean を定義する例です:

package mycode.config;

import java.util.Properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.toshiba.mwcloud.gs.GSException;
import com.toshiba.mwcloud.gs.GridStore;
import com.toshiba.mwcloud.gs.GridStoreFactory;

@Configuration
@PropertySource("classpath:application.properties")
public class GridDBConfig {

 @Value("${GRIDDB_NOTIFICATION_MEMBER}")
  private String notificationMember;

 @Value("${GRIDDB_CLUSTER_NAME}")
  private String clusterName;

 @Value("${GRIDDB_USER}")
  private String user;

 @Value("${GRIDDB_PASSWORD}")
  private String password;

 @Bean
  public GridStore gridStore() throws GSException {
    // Acquiring a GridStore instance
    Properties properties = new Properties();
    properties.setProperty("notificationMember", notificationMember);
    properties.setProperty("clusterName", clusterName);
    properties.setProperty("user", user);
    properties.setProperty("password", password);
    return GridStoreFactory.getInstance().getGridStore(properties);
 }
}

メトリクス収集

次に、リアルタイムのVCSアクティビティ監視のために、GitHubデータの収集、変換、および保存に焦点を当てます。追跡する主要なメトリクスには、コミットプルリクエスト、および開発者の生産性やプロジェクトのアクティビティに関する洞察を提供するその他のGitHubイベントが含まれます。

必要なデータを収集するために、GitHubが提供する特定のAPIエンドポイントを利用します。これらのエンドポイントにより、さまざまな開発アクティビティを追跡できます:

  • Commits:エンドポイント GET /repos/{owner}/{repo}/commits 経由でアクセスでき、指定されたリポジトリ内のコミット一覧を取得します。

  • Pull Requests: エンドポイント GET /repos/{owner}/{repo}/pulls は、プルリクエストに関する情報(現在のステータスを含む)を返します。

  • Repository Events: エンドポイント GET /repos/{owner}/{repo}/events を使用すると、リポジトリ内のすべての重要なアクションを含む包括的なイベント一覧を取得できます。

GridDBへのデータ読み込み

GitHubから必要なデータを収集したら、次にこの情報をGridDBに読み込んで保存し、さらに分析するための準備を行います。以下に、このプロセスの概要を説明します:

以下に、このプロセスの概要を説明します:

  • 認証: GitHubのAPIにアクセスするためのアクセス トークンを使用して認証を行います。このトークンは、リクエストが認証され、必要なデータを取得できることを保証します。したがって、API呼び出しのヘッダーを次のように設定します。
  private HttpHeaders createHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + githubApiToken);
    return headers;
 }
  • データ変換とマッピング: データ取得後、データはGridDBで定義されたスキーマに一致するように変換されます。このステップでは、データのクリーニング、不要な情報のフィルタリング、およびGridDB内の適切なフィールドへのマッピングが行われます。以下は、データベースに格納されるデータの形式を示すVcsActivityDTOです。
package mycode.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;

import com.toshiba.mwcloud.gs.RowKey;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class VcsActivityDTO {
 @RowKey
  public Date timestamp; // Time of the activity
  private String eventType; // Event type: commit, pull request, merge, branch
  private String developerId; // Developer who performed the activity
  private String repositoryId; // Repository ID or name
  private String branch; // Branch associated with the activity
  private String status; // Status (e.g., success, open, merged, conflict)
}
  • データ取り込み: 最後に、変換されたデータはGridDBに読み込まれ、開発者の活動に関するリアルタイム監視と分析に利用可能です。データはデータベースに次のように挿入されます:
   TimeSeries<vcsactivitydto> ts = store.putTimeSeries("vcsData", VcsActivityDTO.class);
    for (VcsActivityDTO activity : result) {
          ts.append(activity);
 }</vcsactivitydto>

以下は、上記で説明したすべてのステップを実装した MetricsCollectionService.java の完全なコードです。

package mycode.service;

import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.toshiba.mwcloud.gs.*;
import mycode.dto.VcsActivityDTO;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;

@Service
public class MetricsCollectionService {
 @Autowired
  GridStore store;

 @Autowired
  RestTemplate restTemplate;

 @Value("${github.api.token}")
  private String githubApiToken;

 @Value("${github.api.base-url}")
  private String githubBaseUrl;

 @Scheduled(fixedRate = 2222260) // Collect metrics every minute
  public void collectMetrics() throws GSException, JsonMappingException, JsonProcessingException, ParseException {
    String repoOwner = "microsoft"; 
    String repoName = "vscode"; 

    List<vcsactivitydto> commits = getCommits(repoOwner, repoName);
   List</vcsactivitydto><vcsactivitydto> pullRequests = getPullRequests(repoOwner, repoName);
    List</vcsactivitydto><vcsactivitydto> branchEvents = getBranchEvents(repoOwner, repoName);

   System.out.println("Collected Commits: " + commits.size());
   System.out.println("Collected Pull Requests: " + pullRequests.size());
    System.out.println("Collected Pull Requests: " + branchEvents.size());
    List</vcsactivitydto><vcsactivitydto> result = new ArrayList<>();
    result.addAll(commits);
    result.addAll(pullRequests);
    result.addAll(branchEvents);
    TimeSeries</vcsactivitydto><vcsactivitydto> ts = store.putTimeSeries("vcsData", VcsActivityDTO.class);
    for (VcsActivityDTO activity : result) {
        ts.append(activity);
 }
 }

  private HttpHeaders createHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + githubApiToken);
    return headers;
 }

  // Method to retrieve commits from a specific repository
  public List</vcsactivitydto><vcsactivitydto> getCommits(String repoOwner, String repoName) throws ParseException {
    String url = UriComponentsBuilder.fromHttpUrl(githubBaseUrl)
 .path("/repos/{owner}/{repo}/commits")
 .buildAndExpand(repoOwner, repoName)
 .toUriString();

    HttpHeaders headers = createHeaders();
    HttpEntity<string> entity = new HttpEntity<>(headers);

    ResponseEntity<list> response = restTemplate.exchange(url, HttpMethod.GET, entity, List.class);
    List<vcsactivitydto> commitActivities = new ArrayList<>();

    List<Map<String, Object>> commits = (List<Map<String, Object>>) response.getBody();
    if (commits != null) {
      for (Map<String, Object> commit : commits) {
        commitActivities.add(mapCommitToVcsActivityDTO(commit));
 }
 }

    return commitActivities;
 }

  public List</vcsactivitydto><vcsactivitydto> getPullRequests(String repoOwner, String repoName) throws ParseException {
    String url = UriComponentsBuilder.fromHttpUrl(githubBaseUrl)
 .path("/repos/{owner}/{repo}/pulls")
 .queryParam("state", "all") // Retrieves both open and closed pull requests
 .buildAndExpand(repoOwner, repoName)
 .toUriString();

    HttpHeaders headers = createHeaders();
    HttpEntity<string> entity = new HttpEntity<>(headers);

    ResponseEntity<list> response = restTemplate.exchange(url, HttpMethod.GET, entity, List.class);
    List<vcsactivitydto> prActivities = new ArrayList<>();

    List<Map<String, Object>> pullRequests = (List<Map<String, Object>>) response.getBody();
    if (pullRequests != null) {
      for (Map<String, Object> pr : pullRequests) {
        prActivities.add(mapPullRequestToVcsActivityDTO(pr));
 }
 }

    return prActivities;
 }

  private VcsActivityDTO mapCommitToVcsActivityDTO(Map<String, Object> commitData) throws ParseException {
    Map<String, Object> commitInfo = (Map<String, Object>) commitData.get("commit");
    Map<String, Object> authorInfo = (Map<String, Object>) commitInfo.get("author");

    String eventType = "Commit";
    String developerId = (String) authorInfo.get("name");
    String repositoryId = (String) commitInfo.get("url"); 
    String branch = "main"; 
    String status = "Success";
    String timestamp = (String) authorInfo.get("date");

    return new VcsActivityDTO(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(timestamp), eventType, developerId, repositoryId, branch, status);
 }

  private VcsActivityDTO mapPullRequestToVcsActivityDTO(Map<String, Object> prData) throws ParseException {
    String eventType = "Pull Request";
    Map<String, Object> userInfo = (Map<String, Object>) prData.get("user");
    String developerId = (String) userInfo.get("login");
    String repositoryId = (String) userInfo.get("repos_url");; 
    String branch = (String) ((Map<String, Object>)prData.get("base")).get("ref"); 
    String status = (String) prData.get("state"); 
    String timestamp = (String) prData.get("created_at");
    return new VcsActivityDTO(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(timestamp), eventType, developerId, repositoryId, branch, status);
 }

  public List</vcsactivitydto><vcsactivitydto> getBranchEvents(String repoOwner, String repoName) throws ParseException{
    String url = UriComponentsBuilder.fromHttpUrl(githubBaseUrl)
 .path("/repos/{owner}/{repo}/events")
 .buildAndExpand(repoOwner, repoName)
 .toUriString();

    HttpHeaders headers = createHeaders();
    HttpEntity<string> entity = new HttpEntity<>(headers);

    ResponseEntity<list> response = restTemplate.exchange(url, HttpMethod.GET, entity, List.class);
    List<vcsactivitydto> branchActivities = new ArrayList<>();

    List<Map<String, Object>> events = response.getBody();
    if (events != null) {
      for (Map<String, Object> eventData : events) {
        String eventType = (String) eventData.get("type");
        if ("CreateEvent".equals(eventType) || "DeleteEvent".equals(eventType)) {
          Map<String, Object> payload = (Map<String, Object>) eventData.get("payload");
          String refType = (String) payload.get("ref_type");
          if ("branch".equals(refType)) {
            String branchName = (String) payload.get("ref");
            String developerId = (String) ((Map<String, Object>)eventData.get("actor")).get("login");  
            String timestamp = (String) eventData.get("created_at");

            // Map to VcsActivityDTO
            VcsActivityDTO activity = new VcsActivityDTO(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(timestamp),
                eventType.equals("CreateEvent") ? "Branch Creation" : "Branch Deletion",
 developerId,
 repoName,
 branchName,
                "Success"
 );
            branchActivities.add(activity);
 }
 }
 }
 }
    return branchActivities;
 }

}</vcsactivitydto></list></string></vcsactivitydto></list></string></vcsactivitydto></list></string></vcsactivitydto>

上記のステップに従うことで、GitHubから開発者の活動に関するデータを効果的に抽出でき、GridDBに読み込むことができます。

GridDBでのデータクエリとThymeleafを使用した可視化

GridDBにデータが格納され利用可能になったら、次にこのデータをアクション可能な洞察を提供する形で可視化します。

このセクションでは、Spring Boot、ThymeleafChart.jsを使用してダッシュボードを構築し、コミットとプルリクエストのトレンドを時間軸に沿って表示するチャートをレンダリングする方法を説明します。

以下の手順でこれを実現します:

  • チャートコントローラーの構築

ChartControllerは、GridDBのバックエンドデータとダッシュボードに表示されるフロントエンドの可視化の間で仲介役を果たします。その役割には、HTTPリクエストの処理、サービス層との連携によるデータの取得、およびそのデータをThymeleafテンプレートに渡しレンダリングを行うことが含まれます。

以下にChartControllerの実装例を示します:

package mycode.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.fasterxml.jackson.databind.ObjectMapper;

import mycode.service.ChartService;
import mycode.dto.VcsActivityDTO;

import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
public class ChartController {

 @Autowired
  ChartService chartService;

 @Autowired
  private ObjectMapper objectMapper;

 @GetMapping("/charts")
  public String showCharts(Model model) {
    try {
      List<vcsactivitydto> events = chartService.getVcsEvents();
      model.addAttribute("events", events);

      // Prepare data for charts
      Map<String, Integer> commitData = prepareCommitData(events);
      Map<String, Integer> prData = preparePullRequestData(events);

      // Convert Maps to JSON Strings for use in JavaScript in the Thymeleaf template
      String commitDataJson = objectMapper.writeValueAsString(commitData);
      String prDataJson = objectMapper.writeValueAsString(prData);

      model.addAttribute("commitDataJson", commitDataJson);
      model.addAttribute("prDataJson", prDataJson);

 } catch (Exception e) {
      e.printStackTrace();
 }
    return "charts";
 }

  private Map<String, Integer> prepareCommitData(List</vcsactivitydto><vcsactivitydto> events) {
    Map<String, Integer> commitMap = new HashMap<>();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 

    for (VcsActivityDTO event : events) {
      if ("Commit".equals(event.getEventType())) {
        String timestamp = dateFormat.format(event.getTimestamp());
        commitMap.put(timestamp, commitMap.getOrDefault(timestamp, 0) + 1);
 }
 }
    return commitMap;
 }

  private Map<String, Integer> preparePullRequestData(List</vcsactivitydto><vcsactivitydto> events) {
    Map<String, Integer> prMap = new HashMap<>();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // Date format

    for (VcsActivityDTO event : events) {
      if ("Pull Request".equals(event.getEventType())) {
        String timestamp = dateFormat.format(event.getTimestamp()); 
        prMap.put(timestamp, prMap.getOrDefault(timestamp, 0) + 1);
 }
 }
    return prMap;
 }
}</vcsactivitydto>
  • チャート サービスの実施

ChartServiceはビジネスロジック層として機能し、GridDBへのクエリ実行と結果の処理に必要な操作をカプセル化します。

ChartServiceクラスは、「vcsData」という名前のGridStoreデータベースコンテナからVCS(バージョン管理システム)イベントを取得します。サービスは結果の各行を処理し、TimestampEventTypeDeveloperIdRepositoryIdBranchStatus などのフィールドを VcsActivityDTO オブジェクトにマッピングします。最後に、これらの DTO のリストを返却し、VCS アクティビティ イベントを表します。

以下に ChartService の実装例を示します:

package mycode.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.toshiba.mwcloud.gs.Container;
import com.toshiba.mwcloud.gs.GridStore;
import com.toshiba.mwcloud.gs.Query;
import com.toshiba.mwcloud.gs.Row;
import com.toshiba.mwcloud.gs.RowSet;

import mycode.dto.VcsActivityDTO;

@Service
public class ChartService {

 @Autowired
  GridStore store;

  public List<vcsactivitydto> getVcsEvents() throws Exception {

    Container container = store.getContainer("vcsData");
    if (container == null) {
      throw new Exception("Container not found.");
 }
    List</vcsactivitydto><vcsactivitydto> eventList = new ArrayList<>();


    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
    Date now = new Date();

    String nowString = dateFormat.format(now);
    String startTime = "1971-12-23T18:18:52.000Z";

    String queryString = "select * where Timestamp >= TIMESTAMP('" + startTime
 + "') and Timestamp <= TIMESTAMP('" + nowString + "')";
    Query<row> query = container.query(queryString);
    RowSet</row><row> rs = query.fetch();

    while (rs.hasNext()) {
      Row row = rs.next();
      VcsActivityDTO event = new VcsActivityDTO();
      event.setTimestamp(row.getTimestamp(0));
      event.setEventType(row.getString(2));
      event.setDeveloperId(row.getString(3));
      event.setRepositoryId(row.getString(4));
      event.setBranch(row.getString(5));
      event.setStatus(row.getString(1));
      eventList.add(event);

 }
    return eventList;
 }

}</row></vcsactivitydto>
  • Thymeleaf を使用したチャート描画

データを取得し処理した後は、Thymeleafテンプレートを使用してダッシュボードにチャートを表示する最終ステップです。ThymeleafはバックエンドデータをHTMLビューにシームレスに統合できるため、動的かつデータ駆動型のアプリケーションに最適な選択肢です。

以下はcharts.htmlの実装例です:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
  <title>VCS Activity Charts</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    /* Center the content and add padding */
    body {
      font-family: Arial, sans-serif;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      margin: 0;
      padding: 20px;
      background-color: #f5f5f5;
 }

    /* Style containers for each chart */
    .chart-container {
      background-color: white;
      border-radius: 8px;
      box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      padding: 20px;
      margin: 20px 0;
      width: 100%;
      max-width: 600px;
 }

    /* Canvas styling with a fixed height */
    canvas {
      width: 100% !important;
      height: 300px !important;
 }

    /* Chart title styling */
    h2 {
      text-align: center;
      color: #333;
      font-size: 24px;
      margin-bottom: 20px;
 }
  </style>
</head>

<body>

  <div class="chart-container">
    <h2>Commit Activity</h2>
    <canvas id="commitChart"></canvas>
  </div>

  <div class="chart-container">
    <h2>Pull Request Activity</h2>
    <canvas id="prChart"></canvas>
  </div>

  <!-- Inline JavaScript block to properly process Thymeleaf variables -->
  <script th:inline="javascript">
    // Thymeleaf will automatically insert the JSON string correctly
    const commitData = [[${ commitDataJson }]];
    const prData = [[${ prDataJson }]];

    // Parse JSON data into JavaScript objects
    const parsedCommitData = JSON.parse(commitData);
    const parsedPrData = JSON.parse(prData);

    // Helper function to sort the data by date keys
    function sortByDate(data) {
      return Object.keys(data)
 .sort((a, b) => new Date(a) - new Date(b))  // Sort the date strings in ascending order
 .reduce((obj, key) => {
          obj[key] = data[key]; // Rebuild the object in sorted order
          return obj;
 }, {});
 }

    // Sort the commit and PR data by date
    const sortedCommitData = sortByDate(parsedCommitData);
    const sortedPrData = sortByDate(parsedPrData);

    // Extract labels (dates) and values (counts) for Commit chart
    const commitLabels = Object.keys(sortedCommitData);
    const commitValues = Object.values(sortedCommitData);

    // Extract labels (dates) and values (counts) for Pull Request chart
    const prLabels = Object.keys(sortedPrData);
    const prValues = Object.values(sortedPrData);

    // Commit Activity Chart
    const commitCtx = document.getElementById('commitChart').getContext('2d');
    const commitChart = new Chart(commitCtx, {
      type: 'line',
      data: {
        labels: commitLabels,
        datasets: [{
          label: 'Commits',
          data: commitValues,
          backgroundColor: 'rgba(75, 192, 192, 0.2)',
          borderColor: 'rgba(75, 192, 192, 1)',
          borderWidth: 2
 }]
 },
      options: {
        responsive: true,
        maintainAspectRatio: true,
        scales: {
          y: {
            beginAtZero: true
 }
 }
 }
 });

    // Pull Request Activity Chart
    const prCtx = document.getElementById('prChart').getContext('2d');
    const prChart = new Chart(prCtx, {
      type: 'line',
      data: {
        labels: prLabels,
        datasets: [{
          label: 'Pull Requests',
          data: prValues,
          backgroundColor: 'rgba(153, 102, 255, 0.2)',
          borderColor: 'rgba(153, 102, 255, 1)',
          borderWidth: 2
 }]
 },
      options: {
        responsive: true,
        maintainAspectRatio: true,
        scales: {
          y: {
            beginAtZero: true
 }
 }
 }
 });
  </script>
</body>

</html>

プロジェクトの実行

プロジェクトを実行するには、以下のコマンドを実行してアプリケーションをビルドし、実行します:

mvn clean install && mvn spring-boot:run  

ダッシュボードへのアクセス

アプリケーションが起動したら、ウェブブラウザを開き、http://localhost:9090 にアクセスします。このURLには、コミット活動とプルリクエスト活動を時間軸で可視化したチャートを表示するThymeleafベースのダッシュボードが表示されます。

このダッシュボードのチャートは、GitHubから取得したデータをアプリケーションのChartServiceで処理して動的にレンダリングされています。

GridDBでのデータ格納:

GridDB Shellツールを使用すると、以下の例のようにコマンドラインから直接データにアクセスし、クエリを実行できます。

結論:

VCS活動の追跡は、チームのプロダクティビティを理解し、開発ワークフローを最適化するために不可欠です。GridDBのタイムシリーズデータ処理の効率性を活用することで、チームはコミット、マージ、プルリクエストなどの重要なイベントを容易に捕捉でき、リアルタイムの洞察と詳細な歴史的分析の両方を可能にします。

GridDBのスケーラビリティと高速なデータ取得により、ソフトウェアチームはトレンドの監視、ボトルネックの解消、協業の改善を実現し、最終的により効率的で生産性の高い開発プロセスを実現できます。

ブログの内容について疑問や質問がある場合は Q&A サイトである Stack Overflow に質問を投稿しましょう。 GridDB 開発者やエンジニアから速やかな回答が得られるようにするためにも "griddb" タグをつけることをお忘れなく。 https://stackoverflow.com/questions/ask?tags=griddb

Leave a Reply

Your email address will not be published. Required fields are marked *