🐕

GraphQL APIの取得データを成形しCSV出力

2022/07/22に公開

はじめに

  • 以下記事において、Suspenseコンポーネントを利用し、SWRライブラリ経由でデータ取得、その取得結果を画面コンポーネントへ表示させた。

https://zenn.dev/hiroharu8864/articles/f92e4d83cd0023

  • 今回は取得したデータの他システム連携を考慮して、ファイル出力(.csv)を実施する。
  • Suspenseタグ内部のコンポーネントのローディングが完了していない場合、propsのfallbackの内容が表示され、取得後にファイルダウンロードリンクを表示させる。データ取得が完了しない限りはリンクが表示されることはない。
  • SWRライブラリ経由でGraphQL APIよりデータ取得 -> オブジェクト多階層構造となっているAPIレスポンスデータ受領 -> ファイルに書き出すデータのみ抽出し、CSVデータへ成形 -> CSVファイル出力

GitHub APIアクセストークン取得

  • https://github.com/settings/tokens よりアクセストークンを取得する。

  • repoの情報を取得できるように権限設定。

  • 取得したアクセストークンを利用し、API経由で情報取得できることを確認する。

    • ghp_token 部分に取得したトークンを入力する。
    • ヘッダーには「Authorization: bearer」部分まで含めて情報が必要。"data"部のデータ取得を確認。
curl --insecure -H "Authorization: bearer ghp_token" -X POST -d " \
 { \
   \"query\": \"query { viewer { login }}\" \
 } \
" https://api.github.com/graphql
{"data":{"viewer":{"login":"hiroharu8864"}}}

SWRライブラリによるGraphQL API呼び出し ./src/hooks/useGetRepos.ts(カスタムフック)

  • 指定ユーザのリポジトリ名と作成日時を取得するクエリを発行する。
  • レスポンスの型は定義する。(GitHubRepos型)
import { GraphQLClient } from "graphql-request";
import useSWR from "swr";
import { GitHubRepos } from "../type/GitHubRepos";

const getLoginUserReposQuery = `
query loginUserRepository($loginUser: String!, $firstFetchNums: Int!) {
  repositoryOwner(login: $loginUser) {
    repositories(privacy: PUBLIC, first: $firstFetchNums) {
      edges{
        node {
          createdAt
          name
        }
      }
    }
  }
}
`;
const loginUserReposName = "hiroharu8864";
const first = 100;

export const useGetRepos = () => {
  const access_token = "ghp_token";
  const client = new GraphQLClient("https://api.github.com/graphql", {
    headers: {
      Authorization: `bearer ${access_token}`,
      "Content-Type": "application/json"
    },
    method: "POST"
  });

  const { data, error } = useSWR<GitHubRepos>(
    [getLoginUserReposQuery, loginUserReposName, first],
    (query, loginUser, firstFetchNums) =>
      client.request(query, {
        loginUser: loginUserReposName,
        firstFetchNums: first
      }),
    {
      suspense: true
    }
  );

  return { data, error };
};

APIレスポンスの型指定 ./src/types/GitHubRepos.ts

  • GraphQL APIの返却される型は、オブジェクトの階層構造となっている。
  • 戻り値の型は自動生成するのがベターだが、ここは敢えて手動で定義する。
  • edges配列の中にNodeオブジェクトが格納されているのではなく、edges配列 -> 番号が付与されたオブジェクト -> Nodeオブジェクトの階層構造となっている。その構造に合わせて型定義する必要がある。

export type GitHubRepos = {
  repositoryOwner: RepositoryOwner;
};
type RepositoryOwner = {
  repositories: RepositoryConnection;
};
type RepositoryConnection = {
  edges: Array<NumObject>;
};
type NumObject = {
  node: Node;
};
type Node = {
  createdAt: string;
  name: string;
};

出力表示するコンポーネント ./src/components/pages/GraphQLFetchCSV.tsx

  • CSV作成・出力には「react-csv」ライブラリを使用。
  • ページ出力後、GraphQL APIのデータ取得が完了するまで、Suspenseコンポーネントによりダウンロードリンクは表示されない。
  • GraphQL APIのデータ取得 -> JSON展開 -> JSON.parse で必要なデータのみ抽出し、CSVライブラリへ渡す。
  • データ抽出対象のオブジェクトを指定してJSON展開する。
import { FC, memo, Suspense, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { CSVLink } from "react-csv";
import moment from "moment";

import { useGetRepos } from "../../hooks/useGetRepos";
import { FormContainer } from "../molecules/FormContainer";
import { MainButton } from "../atoms/MainButton";

const ResultComponent = () => {
  const { data, error } = useGetRepos();
  /**
   * 空白2文字で整形して出力
   * 取得対象のオブジェクト階層を指定して、JSON展開
   */
  const dataReposJson = JSON.stringify(
    data?.repositoryOwner.repositories.edges,
    null,
    2
  );
  console.log(dataReposJson);
  const parseData = JSON.parse(dataReposJson);

  /**
   * CSV用のデータ作成
   */
  const headers = [
    { label: "Repository Name", key: "repositoryname" },
    { label: "Repository Create Date", key: "repositorycreatedate" }
  ];
  const csvdata = parseData.map((repository) => ({
    repositoryname: `${repository.node.name}`,
    repositorycreatedate: `${repository.node.createdAt}`
  }));
  const now = moment().format("YYYYMMDD_HHmmss");

  return (
    <>
      <CSVLink data={csvdata} headers={headers} filename={`${now}.csv`}>
        CSV Files Download
      </CSVLink>
    </>
  );
};

export const GraphQLFetchCSV: FC = memo(() => {
  const navigate = useNavigate();
  const onClickHome = useCallback(() => {
    navigate("/");
  }, [navigate]);
  return (
    <>
      <FormContainer>
        <h3>Result CSV Files</h3>
        <p>APIデータ取得後、リンク出力</p>
        <br />

        <Suspense fallback={<p>...データ取得中</p>}>
          <ResultComponent />
        </Suspense>

        <MainButton onClick={onClickHome}>Go To Home</MainButton>
      </FormContainer>
    </>
  );
});
  • APIデータ取得後、ダウンロードリンクが出力されて、CSVファイル形式で取得できる。

今後の課題

  • GitHub APIの仕様によりデータは100件までしか取得できない。まとめてデータ取得してファイル出力したいので、101件目以降のデータは別途どこかにストックしておく必要がある。その方式については継続検討する。もしくはクエリで一括取得可能かもしれない。
"message": "Requesting 101 records on the `repositories` connection exceeds the `first` limit of 100 records."

Repository

https://github.com/hiroharu8864/react-graphql-csv

Discussion