カスタムフィールドを3カラムで表示する(Redmine View Customize Plugin)

上記の問い合わせの中で、カスタムフィールドを3カラム表示したいといったのがあったので、サンプルコードを書いてみました。

設定内容

  • Path pattern: .*
  • Insertion position: Bottom of issue form
$(function() {

  const field1 = $('#issue_custom_field_values_1');
  const field2 = $('#issue_custom_field_values_2');
  const field3 = $('#issue_custom_field_values_3');

  // Change layout
  const content = $('<div class="splitcontent">')
    .append($('<div class="splitcontentleft">').append(field1.parent()))
    .append($('<div class="splitcontentleft">').append(field2.parent()))
    .append($('<div class="splitcontentright">').append(field3.parent()));

  $('#attributes').append(content);
})

動作

f:id:onozaty:20200119224923p:plain

カスタムフィールドが大量にあって、少しでも詰めて表示したい、、って時には有用かもしれません。

特定のプロジェクトでファイル添付を無効にする(Redmine View Customize Plugin)

この記事はRedmine Advent Calendar 2019 19日目の記事です。

Redmine Users (Japanese)のメーリングリストで流れていた件で、既にJavaScriptで解決する方法が他の方から出ていましたが、CSSでもできるのでCSSで書いてみました。

設定内容

  • Path pattern: .*
  • Insertion position: Head of all pages

下記はaという識別子のプロジェクトに対して無効とする書き方です。

.project-a #issue-form #attachments_form, .project-a #issue-form .attachments_form {
  display: none;
}

動作

下記のような感じで非表示になります。

f:id:onozaty:20191215224743p:plain

チケットの編集時はちょっと微妙でタイトルまでは消しませんでした。ちょうど良いidやclassが振られていなくて、タイトル含めて消そうとすると #issue-form .filedroplistner fieldset:last-child で指定するくらいしかないのですが、必ず最後になるのか確信がもてなかったのでやめました。

f:id:onozaty:20191215224747p:plain

CSSの使いどころ

JavaScriptの方がいろいろなことができるので、View customizeのサンプルもJavaScriptを使ってものが多くなりますが、CSSの方がシンプルに書けることも多いので、シンプルなデザイン的なもの(今回の非表示のように)は、CSSで書くことが多いです。

プロジェクトの識別子のように、いろんな情報がHTML上に付与されているので、一度DOMインスペクタなどで眺めてみると良いと思います。

諭吉佳作/men

この1か月くらい、家では諭吉佳作/menさんの曲を聴きまくっている。それくらいハマっている。

最初に諭吉佳作/menさんを知ったのは、9月にNHK Eテレで放送された「前山田×体育のワンルーム☆ミュージック」だった。インタビューで、「打ち込みだったら腕が3本なければ叩けないような曲も作れる」(もっと違うニュアンスだったかも)ってとこが印象に残っている。

この時点では、名前は知っている(名前の印象はとても強い)くらいでしかない状態で、特に曲を聴いてみようとはならなかった。この時本人が歌っている曲が流れていたら、きっとこの時点ですぐにハマっていたと思う。
このとき番組で流れていたのは楽曲提供していたでんぱ組.incのMVだった。

そのあと、SpotifyでWeekly Buzzか何か聞いているときに、崎山蒼志くんの「むげん・ (with 諭吉佳作/men)」が流れて、この声って諭吉佳作/menさん!?ってなった。
Spotifyにはほとんど曲がなかったので、YouTube、SoundCloudで曲を聴いた。すごくいい。

曲自体も良いのだけど、そこに声が重なってさらに魅力的なものになっている。この曲とこの声が組み合わさることによる相乗効果というかなんというか。
どれもオリジナリティあふれる楽曲で、ビリー・アイリッシュを初めて聞いた時の感覚に似てる。(ビリー・アイリッシュの曲を初めて聞いた時も、とてもインパクト受けた)

一番のお気に入りは「洋装の語る今日は」で、リズミカルに流れる曲の上でさらに流れるような声、、すごくいい。

soundcloud.com

ちなみに、うちの娘たちは「の ぞ き」がお気に入りで、よく口ずさむようになった。

移動中などにも聞きたいので、Spotifyで配信(課金してるので、ネット接続なくても聴ける)するか、アルバム出して欲しいです。もしくはダウンロード販売でも。
同じように思っている人は既にいっぱいいそうなので、すぐ近い将来そうなるんだろうな。とても楽しみ。

プロジェクト切り替え時にウォッチャーを変更する(Redmine View Customize Plugin)

プロジェクト切り替え時にウォッチャーを変えたいのだけどうまくいかないといった問い合わせもらったので、サンプルコード書いてみました。

入力フォーム差し替えと絡んでハマりそうな箇所なので、他の方の参考にもなればと思います。

入力フォームの差し替え

プロジェクトやステータス、トラッカーを切り替えた際には、入力フォーム自体が差し替えられます。サーバ側を呼び出して、受け取ったHTMLの断片で差し替えるようなイメージです。

チケットの題名や説明、カスタムフィールドなどは、空の入力フォームを作った後に、もともと設定されていた情報に移し替えています。
ただ、ウォッチャーのところは、チェックの付与情報自体が付いたHTMLがサーバ側からかえってきて、それをそのまま差し替えるだけです。(サーバ呼び出しの際にフォームの情報も送っている)
そのため、サーバの呼び出しが行われた後に画面側の情報を変えてもそれが反映されません。

ということで、サーバ側呼び出しの前に画面側を変えてあげる必要がありますが、そうなるとイベントのキャプチャフェーズで拾ってあげる必要があります。jQueryではキャプチャフェーズを指定できないので、addEventListenerで指定する形となります。

設定内容

  • Path pattern: /issues/
  • Insertion position: Head of all pages
$(function() {

  const checkWatcher = function(userId) {
    if ($('#issue_watcher_user_ids_' + userId).length == 0) {
      // When the number of users exceeds 20, the check box is not initially displayed, so add a check box.
      const label = $('<label>').attr({
        id: 'issue_watcher_user_ids_' + userId,
        class: 'floating'
      }).append(
        $('<input>').attr({
          type: 'checkbox',
          name: 'issue[watcher_user_ids][]',
          value: userId
        })
      );

      $('#watchers_inputs').append(label);
    }
    
    $('#issue_watcher_user_ids_' + userId + ' input').prop('checked', true);
  }

  const changeWatcher = function() {

    // reset all watchers
    $('input[name="issue[watcher_user_ids][]"]').prop('checked', false);

    switch($('#issue_project_id').val()) {
      case '1':
        checkWatcher(1);
        break;

      case '2':
        checkWatcher(5);
        break;

      case '3':
        checkWatcher(6);
        break;
    }
  };

  const allAttributes = document.getElementById('all_attributes');
  if (!allAttributes) {
    return;
  }

  allAttributes.addEventListener(
    'change',
    function(e) {
      if (e.target.id == 'issue_project_id') {
        changeWatcher();
      }
    },
    // Capturing phase (To work before updateIssueFrom is executed)
    true);
});

追記@2022-12-25

ユーザ数が20を超えていた場合の考慮を追加しました。
20を超えると、ウォッチャーをチェックするためのチェックボックスが未選択のものだと表示されないため、うまく動きませんでした。

gitlab-project-member-exporter - GitLabからプロジェクト毎のメンバー一覧をエクスポートするツールを作った

この記事はGitLab Advent Calendar 2019の2日目の記事です。

GitLabからプロジェクト毎のメンバー一覧をエクスポートするツール、gitlab-project-member-exporter を作りました。

なぜ作ったか

GitLabでグループ、メンバーが多くなってくると、誰がどこにアクセス可能な状態なのかの把握が面倒になってきます。

適当なタイミングで確認しようとしても、プロジェクトやユーザの情報をGitLabの管理画面から見ていくしかありません。数プロジェクトならばどうにかなりそうですが、数十、数百となってくると、ページ辿るだけでも面倒な作業になります。

全プロジェクト×メンバの一覧ができてしまえば、確認が少しは楽になるだろうということで、今回ツールとして作りました。

できること

このツールを使うと、下記のようなCSVファイルをエクスポートできます。プロジェクトとそれに所属するメンバーの一覧になります。

実際の権限と同様に、グループに権限があった場合、その配下のプロジェクトに権限があるものとして一覧化しています。

プロジェクトメンバ一覧

Project: Name Project: Visibility User: Name User: Username User: State Permission
Administrator / my-project private Administrator root active Maintainer
group1 aaa / project-b private Administrator root active Owner
group1 aaa / project-b private user2 user2 active Developer
group1 aaa / project-a private Administrator root active Owner
group1 aaa / project-a private user2 user2 active Developer
group1 aaa / project-a private user1 user1 blocked Maintainer

また、ユーザ一覧、プロジェクト一覧もエクスポートできます。

ユーザ一覧

Name Username Email State Admin Created at Last sign in at Last activity on
user3 user3 user3@example.com active FALSE 2019-11-16T13:37:15.885Z
user2 user2 user2@example.com active FALSE 2019-11-16T13:36:29.651Z
user1 user1 user1@example.com blocked FALSE 2019-11-16T13:35:44.406Z
Administrator root admin@example.com active TRUE 2019-11-16T07:50:47.821Z 2019-11-24T14:50:54.996Z 2019-11-25

プロジェクト一覧

Name Visibility Created at Last activity at
Administrator / my-project private 2019-11-16T13:48:47.727Z 2019-11-16T13:48:47.727Z
group1 / project-b private 2019-11-16T13:48:11.161Z 2019-11-24T15:08:11.024Z
group1 / project-a private 2019-11-16T13:44:20.849Z 2019-11-16T13:44:20.849Z

これら情報を使うことによって、いちいちGitLabのページを辿らなくても、プロジェクトメンバーの状況が一覧で確認できます。
新しいユーザやプロジェクトが追加されたこともわかるので、それらに対してどのように権限が設定されているのか、、といったことも追うことができます。

定期的に出力して、差分を見るというのも良いと思います。

技術的なところ

GitLabのAPIを利用しています。

必要なリソースへアクセスするためのAPIは揃っており、特に悩むことはありませんでした。 (Paginationの情報がレスポンスのヘッダに乗ってくることを見逃していたくらい)

使っているAPIは下記でまとめています。

利用方法

利用方法については、下記READMEをご参照ください。

Javaで実装されています。必要な情報に過不足があるようでしたら、カスタマイズしてご利用ください。

GitLabのAPIを利用して、ユーザが参照できるプロジェクト一覧を作成する

GitLab上で、誰がどのプロジェクトを参照可能かを一覧化したかったので、GitLabのAPIを利用して一覧化してみます。

GitLab Community Editionを利用して確認しました。

GItLabのAPI

APIの情報は下記ドキュメントにまとまっています。

認証方法はいくつかありますが、今回はPersonal access tokenを利用してアクセスしてみます。Personal access tokenはUser Settingsから作成可能です。

Scopeはapiにしました。

グループメンバーの取得

プロジェクトを参照できるのは、下記の2パターンとなります。

  1. 上位のグループのメンバー
  2. プロジェクトのメンバー

そのため、単にプロジェクトのメンバーの情報だけでアクセス可否が決まるのではなく、グループのメンバーの情報も考慮する必要があります。

ということで、まずはAPIでグループ一覧を取得します。

GET http://gitlab.example.com/api/v4/groups?access_token=<access token>

下記のようにグループ一覧が取得できました。

[
  {
    "id": 5,
    "web_url": "http://gitlab.example.com/groups/group1",
    "name": "group1",
    "path": "group1",
    "description": "",
    "visibility": "private",
    "share_with_group_lock": false,
    "require_two_factor_authentication": false,
    "two_factor_grace_period": 48,
    "project_creation_level": "developer",
    "auto_devops_enabled": null,
    "subgroup_creation_level": "maintainer",
    "emails_disabled": null,
    "lfs_enabled": true,
    "avatar_url": null,
    "request_access_enabled": true,
    "full_name": "group1",
    "full_path": "group1",
    "parent_id": null
  },
  {
    "id": 6,
    "web_url": "http://gitlab.example.com/groups/group2",
    "name": "group2",
    "path": "group2",
    "description": "",
    "visibility": "private",
    "share_with_group_lock": false,
    "require_two_factor_authentication": false,
    "two_factor_grace_period": 48,
    "project_creation_level": "developer",
    "auto_devops_enabled": null,
    "subgroup_creation_level": "maintainer",
    "emails_disabled": null,
    "lfs_enabled": true,
    "avatar_url": null,
    "request_access_enabled": true,
    "full_name": "group2",
    "full_path": "group2",
    "parent_id": null
  }
]

ここからさらにグループのメンバーを取得します。

GET http://gitlab.example.com/api/v4/groups/5/members?access_token=<access token>
[
  {
    "id": 1,
    "name": "Administrator",
    "username": "root",
    "state": "active",
    "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
    "web_url": "http://gitlab.example.com/root",
    "access_level": 50,
    "expires_at": null
  },
  {
    "id": 3,
    "name": "user2",
    "username": "user2",
    "state": "active",
    "avatar_url": "https://www.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?s=80&d=identicon",
    "web_url": "http://gitlab.example.com/user2",
    "access_level": 30,
    "expires_at": null
  }
]

これを繰り返し取得することによって、各グループのメンバー一覧が作成できます。

プロジェクトメンバーの取得

プロジェクト一覧を取得します。なお、グループから辿っていく方法もありますが、ユーザが持つプロジェクトも含めたかったので、プロジェクト一覧から辿るようにしました。

GET http://gitlab.example.com/api/v4/projects?simple=true&access_token=<access token>

プロジェクト一覧は下記のようになります。

[
  {
    "id": 3,
    "description": "",
    "name": "my-project",
    "name_with_namespace": "Administrator / my-project",
    "path": "my-project",
    "path_with_namespace": "root/my-project",
    "created_at": "2019-11-16T13:48:47.727Z",
    "default_branch": null,
    "tag_list": [],
    "ssh_url_to_repo": "git@gitlab.example.com:root/my-project.git",
    "http_url_to_repo": "http://gitlab.example.com/root/my-project.git",
    "web_url": "http://gitlab.example.com/root/my-project",
    "readme_url": null,
    "avatar_url": null,
    "star_count": 0,
    "forks_count": 0,
    "last_activity_at": "2019-11-16T13:48:47.727Z",
    "namespace": {
      "id": 1,
      "name": "Administrator",
      "path": "root",
      "kind": "user",
      "full_path": "root",
      "parent_id": null,
      "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
      "web_url": "http://gitlab.example.com/root"
    }
  },
  {
    "id": 2,
    "description": "",
    "name": "project-b",
    "name_with_namespace": "group1 / project-b",
    "path": "project-b",
    "path_with_namespace": "group1/project-b",
    "created_at": "2019-11-16T13:48:11.161Z",
    "default_branch": null,
    "tag_list": [],
    "ssh_url_to_repo": "git@gitlab.example.com:group1/project-b.git",
    "http_url_to_repo": "http://gitlab.example.com/group1/project-b.git",
    "web_url": "http://gitlab.example.com/group1/project-b",
    "readme_url": null,
    "avatar_url": null,
    "star_count": 0,
    "forks_count": 0,
    "last_activity_at": "2019-11-16T13:48:11.161Z",
    "namespace": {
      "id": 5,
      "name": "group1",
      "path": "group1",
      "kind": "group",
      "full_path": "group1",
      "parent_id": null,
      "avatar_url": null,
      "web_url": "http://gitlab.example.com/groups/group1"
    }
  },
  {
    "id": 1,
    "description": "",
    "name": "project-a",
    "name_with_namespace": "group1 / project-a",
    "path": "project-a",
    "path_with_namespace": "group1/project-a",
    "created_at": "2019-11-16T13:44:20.849Z",
    "default_branch": null,
    "tag_list": [],
    "ssh_url_to_repo": "git@gitlab.example.com:group1/project-a.git",
    "http_url_to_repo": "http://gitlab.example.com/group1/project-a.git",
    "web_url": "http://gitlab.example.com/group1/project-a",
    "readme_url": null,
    "avatar_url": null,
    "star_count": 0,
    "forks_count": 0,
    "last_activity_at": "2019-11-16T13:44:20.849Z",
    "namespace": {
      "id": 5,
      "name": "group1",
      "path": "group1",
      "kind": "group",
      "full_path": "group1",
      "parent_id": null,
      "avatar_url": null,
      "web_url": "http://gitlab.example.com/groups/group1"
    }
  }
]

ここからさらにプロジェクトのメンバーを取得します。

GET http://gitlab.example.com/api/v4/projects/1/members?access_token=<access token>
[
  {
    "id": 2,
    "name": "user1",
    "username": "user1",
    "state": "active",
    "avatar_url": "https://www.gravatar.com/avatar/111d68d06e2d317b5a59c2c6c5bad808?s=80&d=identicon",
    "web_url": "http://gitlab.example.com/user1",
    "access_level": 40,
    "expires_at": null
  }
]

この情報に、さらに上位のグループのメンバーの情報を付与することによって、プロジェクトにアクセス可能なユーザ一覧を作ることができます。

おわりに

今回調べた情報を元にツールを作成予定です。作成出来たら追記します。

ShortcutKey2URLでショートカット一覧のエクスポート、インポートに対応しました

要望のあったショートカット一覧のエクスポート、インポートに対応しました。

Firefox版(v4.3.0)、Chrome版(v1.3.0)ともに対応しています。(Chrome版は、この記事を書いているタイミングでは審査待ちです)

Firefox版、Chrome版ともにフォーマットは同じですので、相互で移行が可能になりました。

f:id:onozaty:20191028214240p:plain