オープンソースのWikiであるGROWIにはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。
今回は、GROWIプラグインとしてBacklogと連携するプラグインを作りました。BacklogにはWiki機能がありますが、このプラグインの場合は逆に、GROWI内にプロジェクト管理の情報を取り込んでいく形です。
プラグインの動作
現状用意しているのは、プロジェクトの表示と課題の表示機能です。
プロジェクトの表示
::backlog[projects]{host=example.backlog.com}
hostにはBacklogのURLを指定します。オプションとして、 archived
と all
パラメータが指定できます。
::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キーが保存されます。
プラグインを追加する
利用する際には、GROWIの管理画面の プラグイン
にて追加してください。URLは https://github.com/goofmint/growi-plugin-backlog
です。
コードについて
コードは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>
</>);
同じように、課題一覧も表示します。課題の状況に応じて、次のステータスに移動するためのアイコンを表示しています。
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>
</>);
changeStatus
関数は、課題のステータスを変更して、リロードします。
const changeStatus = async(issue: backlogjs.Entity.Issue.Issue, statusId: number) => {
await backlog.patchIssue(issue.id, {
statusId,
});
window.location.reload();
};
Backlog JS SDKについて
Backlogでは、ブラウザとNode.jsどちらでも使えるSDKが提供されています。本プラグインでも、このSDKを使っています。
nulab/backlog-js: Backlog API version 2 client for browser and node.
GROWIコミュニティについて
プラグインの使い方や要望などがあれば、ぜひGROWIコミュニティにお寄せください。実現できそうなものがあれば、なるべく対応します。他にもヘルプチャンネルなどもありますので、ぜひ参加してください!
まとめ
GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。