0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GROWIでBacklogのデータ表示するプラグインを作りました

Last updated at Posted at 2025-01-12

オープンソースのWikiであるGROWIにはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。

今回は、GROWIプラグインとしてBacklogと連携するプラグインを作りました。BacklogにはWiki機能がありますが、このプラグインの場合は逆に、GROWI内にプロジェクト管理の情報を取り込んでいく形です。

003.jpg

プラグインの動作

現状用意しているのは、プロジェクトの表示と課題の表示機能です。

プロジェクトの表示

::backlog[projects]{host=example.backlog.com}

hostにはBacklogのURLを指定します。オプションとして、 archivedall パラメータが指定できます。

::backlog[projects]{host=example.backlog.com archived=true all=true}

課題の表示

::backlog[issues]{host=example.backlog.com}

hostにはBacklogのURLを指定します。オプションとして、課題一覧の取得 | Backlog Developer API | Nulabのパラメータが指定できます。

::backlog[issues]{host=example.backlog.com sort=cateogry}

特徴

本プラグインでは、BacklogのAPIキーを利用します。デフォルトでは、そのAPIキー入力欄が表示されます。設定すると、localStorageにAPIキーが保存されます。

001.jpg

プラグインを追加する

利用する際には、GROWIの管理画面の プラグイン にて追加してください。URLは https://github.com/goofmint/growi-plugin-backlog です。

Admin

コードについて

コードはgoofmint/growi-plugin-backlog: GROWI Plugin for Backlogにて公開しています。ライセンスはMIT Licenseになります。

最初に :backlog というRemark Directiveを処理します。この記述があれば、 a タグに変換します。他、 {} 内で指定した情報は title に設定します。また、 backlog = true を追加して、他の a タグとの区別をつけています。

export const remarkPlugin: Plugin = () => {
  return (tree: Node) => {
    return visit(tree, 'leafDirective', (node: Node) => {
      const n = node as unknown as GrowiNode;
      if (n.name !== 'backlog') return;
      const data = n.data || (n.data = {});
      // Render your component
      const { value: action } = n.children[0];
      data.hName = 'a'; // Tag name
      data.hChildren = [{ type: 'text', value: action || 'projects' }]; // Children
      // Set properties
      data.hProperties = {
        title: JSON.stringify({ ...n.attributes, ...{ backlog: true } }), // Pass to attributes to the component
      };
    });
  };
};

a タグを表示する際には、APIキーの有無によって処理を分けます。

const apiKey = localStorage.getItem(keyName);
if (!apiKey) {
  return (
    <>
      <p>Backlog APIキーを取得してください</p>
      <div className="mb-3">
        <label htmlFor="backlogApiKey" className="form-label">APIキー</label>
        <input
          type="password"
          className="form-control"
          id="backlogApiKey"
          placeholder="APIキー"
        />
      </div>
      <div className="mb-3">
        <button
          type="submit"
          className="btn btn-primary"
          onClick={save}
        >保存</button>
      </div>
    </>
  );
}

save メソッドでは、APIキーをlocalStorageに保存します。また、画面をリロードします。これは、GROWIプラグインの中では useState が使えないためです。

const save = () => {
  const { value } = document.querySelector('#backlogApiKey') as HTMLInputElement;
  localStorage.setItem(keyName, value);
  // reload
  window.location.reload();
};

表示処理について

APIキーがあれば、データを取得して表示します。以下はプロジェクト一覧の例です。

const backlog = new backlogjs.Backlog({ host, apiKey });
const getProjects = ({ params }: any) => {
  return backlog.getProjects(params);
};

return (<>
  <Async promiseFn={getProjects} params={params}>
    {({ data, error, isPending }) => {
      if (isPending) return 'Loading...';
      if (error) return `Something went wrong: ${error.message}`;
      if (data) {
        return (<>
          {deleteButton()}
          <table className="table">
            <thead>
              <tr>
                <td>名前</td>
                <td>Wiki</td>
                <td>ファイル共有</td>
                <td>Git</td>
                <td>アーカイブ</td>
              </tr>
            </thead>
            <tbody>
              { data.map((project, i) => (
                <tr
                  key={i}
                >
                  <td>
                    <a
                      href={`https://${host}/projects/${project.id}`} target='_blank'
                    >{ project.name }</a>
                  </td>
                  <td>{ project.useWiki ? '' : '' }</td>
                  <td>{ project.useFileSharing ? '' : '' }</td>
                  <td>{ project.useGit ? '' : '' }</td>
                  <td>{ project.archived ? '' : '' }</td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
        );
      }
    }}
  </Async>
</>);

image.png

同じように、課題一覧も表示します。課題の状況に応じて、次のステータスに移動するためのアイコンを表示しています。

const getIssues = ({ params }: any) => {
  return backlog.getIssues(params);
};

return (<>
  <Async promiseFn={getIssues} params={params}>
    {({ data, error, isPending }) => {
      if (isPending) return 'Loading...';
      if (error) return `Something went wrong: ${error.message}`;
      if (data) {
        return (<>
          {deleteButton()}
          <table className="table">
            <thead>
              <tr>
                <td>名前</td>
                <td>ステータス</td>
                <td>アサイン</td>
                <td>期限</td>
                <td>アクション</td>
              </tr>
            </thead>
            <tbody>
              { data.map((issue, i) => (
                <tr
                  key={i}
                >
                  <td><a
                    href={`https://${host}/view/${issue.issueKey}`} target='_blank'
                  >
                    { issue.summary }
                  </a></td>
                  <td>{ issue.status.name }</td>
                  <td>{ issue.assignee?.name }</td>
                  <td>{ issue.dueDate ? (new Date(issue.dueDate)).toLocaleDateString() : '' }</td>
                  <td>
                    { issue.status.id === 1 && <>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        onClick={() => changeStatus(issue, 2)}
                        width="24px" height="24px" viewBox="0 0 24 24">
                        <path fill="#fff" d="M14.3 21.7c-.7.2-1.5.3-2.3.3c-5.5 0-10-4.5-10-10S6.5 2 12 2c1.3 0 2.6.3 3.8.7l-1.6 1.6c-.7-.2-1.4-.3-2.2-.3c-4.4 0-8 3.6-8 8s3.6 8 8 8c.4 0 .9 0 1.3-.1c.2.7.6 1.3 1 1.8M7.9 10.1l-1.4 1.4L11 16L21 6l-1.4-1.4l-8.6 8.6zM18 14v3h-3v2h3v3h2v-3h3v-2h-3v-3z" />
                      </svg>
                    </>}
                    { issue.status.id === 2 && <>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width={24} height={24}
                        onClick={() => changeStatus(issue, 3)}
                        viewBox="0 0 24 24">
                        <path fill="currentColor"
                          d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"></path>
                      </svg>
                    </>}
                    { issue.status.id === 3 && <>
                      <svg
                        xmlns="http://www.w3.org/2000/svg"
                        width="24px" height="24px"
                        onClick={() => changeStatus(issue, 4)}
                        viewBox="0 0 1024 1024">
                        <path
                          fill="#fff"
                          d={'M280.768 753.728L691.456 167.04a32 32 0 1 1 52.416 36.672L314.24 817.472a32 32 0 0 1-45.44 7.296l-230.4-172.8a32 32 0 0 1 38.4-51.2zM736 448a32 32 0 1 1 0-64h192a32 32 0 1 1 0 64zM608 640a32 32 0 0 1 0-64h319.936a32 32 0 1 1 0 64zM480 832a32 32 0 1 1 0-64h447.936a32 32 0 1 1 0 64z'}
                        />
                      </svg>
                    </>}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
        );
      }
    }}
  </Async>
</>);

image.png

changeStatus 関数は、課題のステータスを変更して、リロードします。

const changeStatus = async(issue: backlogjs.Entity.Issue.Issue, statusId: number) => {
  await backlog.patchIssue(issue.id, {
    statusId,
  });
  window.location.reload();
};

スクリーンショット_2025-01-12_15_14_19.png

Backlog JS SDKについて

Backlogでは、ブラウザとNode.jsどちらでも使えるSDKが提供されています。本プラグインでも、このSDKを使っています。

nulab/backlog-js: Backlog API version 2 client for browser and node.

GROWIコミュニティについて

プラグインの使い方や要望などがあれば、ぜひGROWIコミュニティにお寄せください。実現できそうなものがあれば、なるべく対応します。他にもヘルプチャンネルなどもありますので、ぜひ参加してください!

GROWI Slackへの参加はこちらから

まとめ

GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。

OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?